【问题标题】:How to draw heart shape in UIView (iOS)?如何在 UIView (iOS) 中绘制心形?
【发布时间】:2015-03-24 08:24:07
【问题描述】:

我想创建不同的形状。下图是示例之一。你能建议我如何改变形状吗?

【问题讨论】:

    标签: ios core-graphics cgcontext uibezierpath


    【解决方案1】:

    当我自己尝试绘制一个基于 UIBezier 的心时遇到了这个问题,但我正在寻找一个看起来更像 Instagram 帖子上的心。

    我最终为它编写了自己的扩展/类,所以我想我会在这里分享它,以防它对将来偶然发现它的任何人有用。

    (这些都在 Swift 3 中)

    首先,这是 UIBezier 扩展:

    extension UIBezierPath {
        convenience init(heartIn rect: CGRect) {
            self.init()
    
            //Calculate Radius of Arcs using Pythagoras
            let sideOne = rect.width * 0.4
            let sideTwo = rect.height * 0.3
            let arcRadius = sqrt(sideOne*sideOne + sideTwo*sideTwo)/2
    
            //Left Hand Curve
            self.addArc(withCenter: CGPoint(x: rect.width * 0.3, y: rect.height * 0.35), radius: arcRadius, startAngle: 135.degreesToRadians, endAngle: 315.degreesToRadians, clockwise: true)
    
            //Top Centre Dip
            self.addLine(to: CGPoint(x: rect.width/2, y: rect.height * 0.2))
    
            //Right Hand Curve
            self.addArc(withCenter: CGPoint(x: rect.width * 0.7, y: rect.height * 0.35), radius: arcRadius, startAngle: 225.degreesToRadians, endAngle: 45.degreesToRadians, clockwise: true)
    
            //Right Bottom Line
            self.addLine(to: CGPoint(x: rect.width * 0.5, y: rect.height * 0.95))
    
            //Left Bottom Line
            self.close()
        }
    }
    

    您还需要在项目中的某处添加它,以便 degreeToRadians 扩展工作:

    extension Int {
        var degreesToRadians: CGFloat { return CGFloat(self) * .pi / 180 }
    }
    

    使用它就像初始化 UIBezier 一样简单,就像初始化椭圆一样:

    let bezierPath = UIBezierPath(heartIn: self.bounds)
    

    除此之外,我还创建了一个类来帮助轻松地显示它并控制某些功能。它将在 IB 中完全渲染,并覆盖填充颜色(使用 tint color 属性)、是否需要填充、笔触颜色和笔触宽度:

    @IBDesignable 类 HeartButton: UIButton {

    @IBInspectable var filled: Bool = true
    @IBInspectable var strokeWidth: CGFloat = 2.0
    
    @IBInspectable var strokeColor: UIColor?
    
    override func draw(_ rect: CGRect) {
        let bezierPath = UIBezierPath(heartIn: self.bounds)
    
        if self.strokeColor != nil {
            self.strokeColor!.setStroke()
        } else {
            self.tintColor.setStroke()
        }
    
        bezierPath.lineWidth = self.strokeWidth
        bezierPath.stroke()
    
        if self.filled {
            self.tintColor.setFill()
            bezierPath.fill()
        }
    }
    

    }

    这是一个 UIButton 类,但如果你不想让它成为一个按钮,改变它会很简单。

    实际上是这样的:

    只需一击:

    【讨论】:

    • 你是如何在代码中调用这个类来绘制的?我使用 let heartShape = HeartButton(),然后使用 heartView.addSubview(heartShape) 将 heartShape 添加到视图中,但它没有绘制
    • 这段代码有一个错误:假设矩形的原点是(0, 0)。请解决这个问题。
    【解决方案2】:

    您可以尝试 PaintCode

    绘制它,PaintCode 为您生成 UIBezierPath。

    http://www.paintcodeapp.com/

    【讨论】:

    • @RajeshMaurya 有一个功能有限的免费试用版(保存等)
    • @Jai 你可以谷歌“heart svg”然后导入文件。
    【解决方案3】:

    所以,如您所见,有 2 个简单的数学函数。你只需要在drawRect中绘制它: 或者,您可以轻松使用CorePlot frameworkUseful example.

    【讨论】:

    • 这是什么绘制函数图形图像?
    • (1-(abs(x)-1)^2)^(1/2) - 蓝色图和 arccos(1-abs(x))-pi - 红色图
    • 你是如何创建这个界面的?它像 Xcode 中的编辑器一样工作吗?
    • 没有。这只是在线图形生成器的示例
    • 你能给出网站的名称吗?你知道 Xcode 的其他图形生成器吗?
    【解决方案4】:

    您必须创建一个 UIBezierPath。 查看文档:https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIBezierPath_class/index.html

    以及绘图指南中 Apple 的贝塞尔曲线部分: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/BezierPaths/BezierPaths.html

    你可以从画一个弧开始

    +bezierPathWithArcCenter: 
    

    然后添加另一个弧。 现在使用

    将线绘制到底部峰值
    -addCurveToPoint:controlPoint1:controlPoint2:
    

    使用控制点使线条像图像中一样弯曲。 该方法的 UIBezierPath 文档有一个描述用法的示例图像。

    最后将另一行添加回起点并关闭路径。

    链接的文档还向您展示了如何绘制路径,例如在drawRect中。 另一种选择是使用 CAShapeLayer 直接显示路径。

    另一种解决方案是使用像 PaintCode 这样的软件, 这样可以直观地绘制图像并生成代码,而不是自己编写。

    【讨论】:

      【解决方案5】:

      对于其他入侵者,这是 UIBezierPath 的扩展,用于绘制心形。

      从这个组件中提取 https://github.com/ipraba/EPShapes

       public extension UIBezierPath  {
      
          func getHearts(originalRect: CGRect, scale: Double) -> UIBezierPath {
      
          //Scaling will take bounds from the originalRect passed
          let scaledWidth = (originalRect.size.width * CGFloat(scale))
          let scaledXValue = ((originalRect.size.width) - scaledWidth) / 2
          let scaledHeight = (originalRect.size.height * CGFloat(scale))
          let scaledYValue = ((originalRect.size.height) - scaledHeight) / 2
      
          let scaledRect = CGRect(x: scaledXValue, y: scaledYValue, width: scaledWidth, height: scaledHeight)
          self.moveToPoint(CGPointMake(originalRect.size.width/2, scaledRect.origin.y + scaledRect.size.height))
      
          self.addCurveToPoint(CGPointMake(scaledRect.origin.x, scaledRect.origin.y + (scaledRect.size.height/4)),
              controlPoint1:CGPointMake(scaledRect.origin.x + (scaledRect.size.width/2), scaledRect.origin.y + (scaledRect.size.height*3/4)) ,
              controlPoint2: CGPointMake(scaledRect.origin.x, scaledRect.origin.y + (scaledRect.size.height/2)) )
      
          self.addArcWithCenter(CGPointMake( scaledRect.origin.x + (scaledRect.size.width/4),scaledRect.origin.y + (scaledRect.size.height/4)),
              radius: (scaledRect.size.width/4),
              startAngle: CGFloat(M_PI),
              endAngle: 0,
              clockwise: true)
      
          self.addArcWithCenter(CGPointMake( scaledRect.origin.x + (scaledRect.size.width * 3/4),scaledRect.origin.y + (scaledRect.size.height/4)),
              radius: (scaledRect.size.width/4),
              startAngle: CGFloat(M_PI),
              endAngle: 0,
              clockwise: true)
      
          self.addCurveToPoint(CGPointMake(originalRect.size.width/2, scaledRect.origin.y + scaledRect.size.height),
              controlPoint1: CGPointMake(scaledRect.origin.x + scaledRect.size.width, scaledRect.origin.y + (scaledRect.size.height/2)),
              controlPoint2: CGPointMake(scaledRect.origin.x + (scaledRect.size.width/2), scaledRect.origin.y + (scaledRect.size.height*3/4)) )
          self.closePath()
          return self
        }
      }
      

      以 0.7 的比例绘制心脏(原始矩形的 70%)

      【讨论】:

        【解决方案6】:

        Swift 4 版本来自https://github.com/ipraba/EPShapes

        func getHearts(_ originalRect: CGRect, scale: Double) -> UIBezierPath {
        
        
            let scaledWidth = (originalRect.size.width * CGFloat(scale))
            let scaledXValue = ((originalRect.size.width) - scaledWidth) / 2
            let scaledHeight = (originalRect.size.height * CGFloat(scale))
            let scaledYValue = ((originalRect.size.height) - scaledHeight) / 2
        
            let scaledRect = CGRect(x: scaledXValue, y: scaledYValue, width: scaledWidth, height: scaledHeight)
        
            self.move(to: CGPoint(x: originalRect.size.width/2, y: scaledRect.origin.y + scaledRect.size.height))
        
        
            self.addCurve(to: CGPoint(x: scaledRect.origin.x, y: scaledRect.origin.y + (scaledRect.size.height/4)),
                controlPoint1:CGPoint(x: scaledRect.origin.x + (scaledRect.size.width/2), y: scaledRect.origin.y + (scaledRect.size.height*3/4)) ,
                controlPoint2: CGPoint(x: scaledRect.origin.x, y: scaledRect.origin.y + (scaledRect.size.height/2)) )
        
            self.addArc(withCenter: CGPoint( x: scaledRect.origin.x + (scaledRect.size.width/4),y: scaledRect.origin.y + (scaledRect.size.height/4)),
                radius: (scaledRect.size.width/4),
                startAngle: CGFloat(Double.pi),
                endAngle: 0,
                clockwise: true)
        
            self.addArc(withCenter: CGPoint( x: scaledRect.origin.x + (scaledRect.size.width * 3/4),y: scaledRect.origin.y + (scaledRect.size.height/4)),
                radius: (scaledRect.size.width/4),
                startAngle: CGFloat(Double.pi),
                endAngle: 0,
                clockwise: true)
        
            self.addCurve(to: CGPoint(x: originalRect.size.width/2, y: scaledRect.origin.y + scaledRect.size.height),
                controlPoint1: CGPoint(x: scaledRect.origin.x + scaledRect.size.width, y: scaledRect.origin.y + (scaledRect.size.height/2)),
                controlPoint2: CGPoint(x: scaledRect.origin.x + (scaledRect.size.width/2), y: scaledRect.origin.y + (scaledRect.size.height*3/4)) )
        
            self.close()
        
            return self
        }
        

        【讨论】:

          【解决方案7】:

          我喜欢 Wolfram 中的心形:https://mathworld.wolfram.com/HeartCurve.html

          它是具有以下等式的那个,并且是该页面上他们的心形集合的右下角示例。

          x = 16sin^3t
          y = 13cost-5cos(2t)-2cos(3t)-cos(4t)。

          在搜索过程中,我注意到网络上没有很多示例可以绘制这种精确的形状,但我确实有一个用 Swift 编写的独特解决方案,可用于获得精确的心形。我将向您展示如何使用 2 像素线宽绘制心脏轮廓。此解决方案使用专门创建的 UIImage 来保存图像。我将该图像初始化为具有清晰的背景,以便它可以覆盖到任何其他内容上。由于我只是跟踪边界,因此内部是透明的。如果您希望它填充(不透明),那么不要抚摸路径,而是填充它。

          我将分享代码,这些代码使用变量来定义心形的中心、比例以及所需的 X 和 Y 偏移量,以在 UIImage 中移动心形。建议您从至少 200 x 200 大小的 UIImage 开始,然后在 Xcode 调试器中检查图像,看看您想要的大小是否居中,并且具有良好的比例。根据需要调整变量以获得所需的大小。

          顺便说一句,考虑将生成的 UIImage 填充到 UIImageView 中以进行动画处理。

          首先,获取一个空白的 UIImage。我在任何函数之外声明我的,所以我可以轻松使用它。

          var myHeartUIImage = UIImage()
          

          在 viewDidLoad 内部,我将该图像初始化为具有清晰的背景,并为其指定大小。我所有的变量都是为了让心脏在 50 x 50 帧中看起来不错。

              // First get a non nil image, so I just put a clear background inside and it is not nil anymore.
              myHeartUIImage = UIImage(color: .clear, size: CGSize(width: 50, height: 50))!
          
              // Now draw the heart on top of the clear backbround
              myHeartUIImage = createAHeartTrace(startingImage: myHeartUIImage)
          

          这里是 createAHeartTrace 的代码。它使用心脏边界上的 360 个点进行绘制。这对我的使用和规模来说很好。如果您在 iPad Pro 等高分辨率设备上显示大心脏,您可能会将绘图点提高到 1000 或更多。请注意,moveToPoint 提供了第一个点,因此循环仅进行 1...359 即可获得总共 360 个点。

          public func createAHeartTrace(startingImage: UIImage) -> UIImage
          {
              // Create a context of the starting image size and set it as the current one
              UIGraphicsBeginImageContext(startingImage.size)
          
              let centeringValueForX : CGFloat = 9
              let centeringValueForY : CGFloat = -24
          
              let centerPointXYValue : CGFloat = 60.0
              let scaleFactorHere : CGFloat = 1.85
          
              var pointOnHeart = getPointOnHeartShape(thisAngle: 0, thisCenter: CGPoint(x: centerPointXYValue, y: centerPointXYValue), thisScaleFactor: scaleFactorHere)
          
              // Get the current context
              let context = UIGraphicsGetCurrentContext()!
          
              context.setLineWidth(2.0)
              context.setStrokeColor(UIColor.white.cgColor)
              context.move(to: CGPoint(x: pointOnHeart.x - centeringValueForX, y: pointOnHeart.y + centeringValueForY))
          
              for angle in 1...359 {
          
                  pointOnHeart = getPointOnHeartShape(thisAngle: CGFloat(angle), thisCenter: CGPoint(x: centerPointXYValue, y: centerPointXYValue), thisScaleFactor: scaleFactorHere)
          
                  context.addLine(to: CGPoint(x: pointOnHeart.x - centeringValueForX, y: pointOnHeart.y + centeringValueForY))
              }
          
              context.strokePath()
          
              // Save the context as a new UIImage
              var myImage = UIGraphicsGetImageFromCurrentImageContext()
              UIGraphicsEndImageContext()
          
              // Return modified image
              return myImage!
          
          }// ends createAHeartTrace
          

          请注意,createAHeartTrace 总共调用了 func getPointOnHeartShape 360​​ 次。这是getPointOnHeartShape:

          public func getPointOnHeartShape(thisAngle : CGFloat, thisCenter: CGPoint, thisScaleFactor: CGFloat) -> CGPoint
          {
              var tempAngle : CGFloat
              tempAngle = thisAngle.degreesToRadians
          
              let cubedTerm = sin(tempAngle) * sin(tempAngle) * sin(tempAngle)
          
              let pointX : CGFloat = thisCenter.x + thisScaleFactor * 16.0 * cubedTerm
          
              let pointY : CGFloat = thisCenter.y + thisScaleFactor * (-13*(cos(tempAngle)) + 5*(cos(2*tempAngle)) + 2*(cos(3*tempAngle)) + 1*(cos(4*tempAngle)))
          
              let returnPoint = CGPoint(x: pointX, y: pointY)
          
              return returnPoint
          
          } // ends getPointOnHeartShape
          

          您可能需要扩展来将度数转换为弧度。

          extension FloatingPoint 
          {
              var degreesToRadians: Self { return self * .pi / 180 }
          }
          

          作为这些相同行的奖励代码,如果您想绘制一个 5 点星(具有与美国国旗上相同的清晰形状),则下面的 func 效果很好。同样,可以根据需要进行缩放和偏移调整,并且绘图是具有透明中心区域的边框轨迹。其余的设计完全相同。创建一个非 nil UIImage 来保存绘图,然后使用 createAFivePointStar 将其绘制到其中。根据需要选择笔触颜色。

          public func createAFivePointStar(startingImage: UIImage) -> UIImage
          {
              let drawingScaleFactor : CGFloat = 0.11
          
              // Create a context of the starting image size and set it as the current one
              UIGraphicsBeginImageContext(startingImage.size)
          
              // Get the current context
              let context = UIGraphicsGetCurrentContext()!
          
              let centeringValueForX : CGFloat = 25
              let centeringValueForY : CGFloat = 16
          
              context.setLineWidth(4.0)
              context.setStrokeColor(UIColor.white.cgColor)
              context.move(to: CGPoint(x: 694 * drawingScaleFactor - centeringValueForX, y: 106 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 750 * drawingScaleFactor - centeringValueForX, y: 267 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 920 * drawingScaleFactor - centeringValueForX, y: 267 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 789 * drawingScaleFactor - centeringValueForX, y: 372 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 835 * drawingScaleFactor - centeringValueForX, y: 530 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 694 * drawingScaleFactor - centeringValueForX, y: 438 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 553 * drawingScaleFactor - centeringValueForX, y: 530 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 599 * drawingScaleFactor - centeringValueForX, y: 372 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 468 * drawingScaleFactor - centeringValueForX, y: 267 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 637 * drawingScaleFactor - centeringValueForX, y: 267 * drawingScaleFactor + centeringValueForY))
              context.addLine(to: CGPoint(x: 694 * drawingScaleFactor - centeringValueForX, y: 106 * drawingScaleFactor + centeringValueForY))
              context.strokePath()
          
              // Save the context as a new UIImage
              var myImage = UIGraphicsGetImageFromCurrentImageContext()
              UIGraphicsEndImageContext()
          
              // Return modified image
              return myImage!
          
          } // ends createAFivePointStar
          

          【讨论】:

            【解决方案8】:

            这是一个适用于 SwiftUI 和 Swift 5.2 的版本

            注意addArc顺时针方向是反的

            struct Heart : Shape {
              func path(in rect: CGRect) -> Path {
                var path = Path()
            
                path.move(to: CGPoint(x: rect.midX, y: rect.maxY ))
            
                path.addCurve(to: CGPoint(x: rect.minX, y: rect.height/4),
                    control1:CGPoint(x: rect.midX, y: rect.height*3/4) ,
                    control2: CGPoint(x: rect.minX, y: rect.midY) )
            
            
                path.addArc(center: CGPoint( x: rect.width/4,y: rect.height/4),
                    radius: (rect.width/4),
                    startAngle: Angle(radians: Double.pi),
                    endAngle: Angle(radians: 0),
                    clockwise: false)
            
                path.addArc(center: CGPoint( x: rect.width * 3/4,y: rect.height/4),
                    radius: (rect.width/4),
                    startAngle: Angle(radians: Double.pi),
                    endAngle: Angle(radians: 0),
                    clockwise: false)
            
                path.addCurve(to: CGPoint(x: rect.midX, y: rect.height),
                              control1: CGPoint(x: rect.width, y: rect.midY),
                              control2: CGPoint(x: rect.midX, y: rect.height*3/4) )
            
                 return path
              }
            }
            

            【讨论】:

              猜你喜欢
              • 2014-02-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-03-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-02-27
              相关资源
              最近更新 更多