如何在UIView(iOS)中绘制心形?

时间:2015-03-24 08:24:08

标签: ios core-graphics cgcontext uibezierpath

我想创造不同的形状。下图是一个例子。你会建议我如何使用不同的形状吗?

enter image description here

8 个答案:

答案 0 :(得分:10)

当我试图自己绘制一个基于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()
    }
}

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

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

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

let bezierPath = UIBezierPath(heartIn: self.bounds)

除此之外,我还上了一堂课,帮助您轻松显示并控制某些功能。它将在IB中完全渲染,覆盖填充颜色(使用色调颜色属性),无论是否要填充,笔触颜色和笔触宽度:

@IBDesignable class 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类,但如果你不想让它成为一个按钮,那么改变它就会非常简单。

以下是它的实际情况:

Heart Filled

只是中风:

Heart Stroked

答案 1 :(得分:6)

您可以尝试 PaintCode

绘制它,PaintCode为您生成UIBezierPath。

http://www.paintcodeapp.com/

enter image description here

答案 2 :(得分:3)

您必须创建UIBezierPath。 请查看文档:{​​{3}}

和绘图指南中Apple的bezier部分: https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIBezierPath_class/index.html

您可以先用

绘制一个弧线
+bezierPathWithArcCenter: 

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

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

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

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

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

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

答案 3 :(得分:3)

enter image description here

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

答案 4 :(得分:3)

对于其他侵入者,这里是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%)

enter image description here

答案 5 :(得分:1)

https://github.com/ipraba/EPShapes中的Swift 4版本

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
}

答案 6 :(得分:1)

这是适用于带有Swift 5.2的SwiftUI的版本

请注意,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
  }
}

答案 7 :(得分:0)

我喜欢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调试器中检查图像,以查看所需大小是否居中且缩放比例合适。根据需要调整变量以获得所需的心形尺寸。

The UIImage with the heart trace, and transparent background, as seen in the debugger.

顺便说一句,考虑将生成的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点星形(形状与美国国旗相同的清晰形状),则下面的功能非常适用。同样,可以根据需要进行缩放和偏移以进行调整,并且图形是带有透明中心区域的边框的痕迹。设计的其余部分完全相同。创建一个非nil UIImage来保存图形,然后使用createAFivePointStar将其绘制到其中。根据需要选择笔触颜色。

The star shape, as seen in the debugger.

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