如何将圆角添加到 UIBezierPath 自定义矩形?

Posted

技术标签:

【中文标题】如何将圆角添加到 UIBezierPath 自定义矩形?【英文标题】:How to add rounded corner to a UIBezierPath custom rectangle? 【发布时间】:2015-07-03 21:57:15 【问题描述】:

我设法创建了圆角,但我在第一个圆角(右下)遇到问题

问题:

我可以在 (moveToPoint) 方法之前添加 (addArcWithCenter) 方法吗? 如何去掉矩形开头的直线(右下角)?

这是我的自定义矩形代码和屏幕截图:

let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 300, y: 0))
path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2)  , clockwise: true) //1st rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 50), radius:10, startAngle: CGFloat(2 * M_PI / 3), endAngle:CGFloat(M_PI) , clockwise: true)// 2rd rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 10), radius:10, startAngle: CGFloat(M_PI), endAngle:CGFloat(3 * M_PI / 2), clockwise: true)// 3rd rounded corner
// little triangle at the bottom
path.addLineToPoint(CGPoint(x:240 , y:0))
path.addLineToPoint(CGPoint(x: 245, y: -10))
path.addLineToPoint(CGPoint(x:250, y: 0))
path.addArcWithCenter(CGPoint(x: 290, y: 10), radius: 10, startAngle: CGFloat(3 * M_PI / 2), endAngle: CGFloat(2 * M_PI ), clockwise: true)
path.closePath()

【问题讨论】:

那么剩下的呢?你实际上得到了类似的效果。 【参考方案1】:

我认为您正在做的事情过于复杂。 UIBezierPath 为您提供UIBezierPath(roundedRect:) 那么为什么不使用它呢?描边圆角矩形;擦除你要放置小三角形的位置;添加三角形;填充复合路径;并描边三角形缺失的两条边。像这样(这只是我碰巧有的一些代码 - 当然,您应该更改数字以适合您的形状):

let con = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(con, 10, 10)
UIColor.blueColor().setStroke()
UIColor.blueColor().colorWithAlphaComponent(0.4).setFill()
let p = UIBezierPath(roundedRect: CGRectMake(0,0,250,180), cornerRadius: 10)
p.stroke()
CGContextClearRect(con, CGRectMake(20,170,10,11))
let pts = [
    CGPointMake(20,180), CGPointMake(20,200),
    CGPointMake(20,200), CGPointMake(30,180)
]
p.moveToPoint(pts[0])
p.addLineToPoint(pts[1])
p.addLineToPoint(pts[3])
p.fill()
CGContextStrokeLineSegments(con, pts, 4)

【讨论】:

如果您打算制作“strokeStart”和“strokeEnd”动画,那么不使用它的一个很好的理由是。在这种情况下,矩形开始画线的“位置”很重要。使用路径时,您可以控制它。【参考方案2】:

几个观察:

    确保您使用视图bounds 并将其插入线宽的一半。这确保了整个描边边框落在视图的bounds 内。如果你的线宽为 1,这可能不那么明显,但线宽越大,问题就越明显。

    如果使用draw(_:) 方法,请不要使用传递给此方法的rect,而是参考bounds(插入,如上所述)。传递给draw(_:)CGRect 是正在绘制的矩形,不一定是完整的bounds。 (通常是,但并非总是如此,所以总是引用视图的bounds,而不是传递给此方法的rect。)

    正如the documentation 所说(强调):

    需要更新的视图边界部分。第一次绘制视图时,这个矩形通常是视图的整个可见边界。 但是,在后续的绘图操作中,矩形可能只指定了视图的一部分。

    我会给视图的所有各种属性一个didSet 观察者,它将触发视图被重绘。这样,任何 IB 覆盖或以编程方式设置的值都将自动反映在结果视图中。

    1234563这不是必需的,但如果您想在故事板或 NIB 中看到它的呈现,它会很有用。

    虽然您可以使用圆弧圆角,但使用四边形曲线更容易,恕我直言。您只需指定圆弧的结束位置和矩形的角,二次贝塞尔曲线将产生一个很好的圆角。使用这种技术,不需要计算角度或圆弧的中心。

因此:

@IBDesignable
class BubbleView: UIView 
    @IBInspectable var lineWidth:    CGFloat = 1        didSet  setNeedsDisplay()  
    @IBInspectable var cornerRadius: CGFloat = 10       didSet  setNeedsDisplay()  
    @IBInspectable var calloutSize:  CGFloat = 5        didSet  setNeedsDisplay()  
    @IBInspectable var fillColor:    UIColor = .yellow  didSet  setNeedsDisplay()  
    @IBInspectable var strokeColor:  UIColor = .black   didSet  setNeedsDisplay()  

    override func draw(_ rect: CGRect) 
        let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
        let path = UIBezierPath()

        // lower left corner
        path.move(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY - calloutSize))
        path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - calloutSize - cornerRadius),
                          controlPoint: CGPoint(x: rect.minX, y: rect.maxY - calloutSize))

        // left
        path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))

        // upper left corner
        path.addQuadCurve(to: CGPoint(x: rect.minX + cornerRadius, y: rect.minY),
                          controlPoint: CGPoint(x: rect.minX, y: rect.minY))

        // top
        path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY))

        // upper right corner
        path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.minY + cornerRadius),
                          controlPoint: CGPoint(x: rect.maxX, y: rect.minY))

        // right
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - calloutSize - cornerRadius))

        // lower right corner
        path.addQuadCurve(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - calloutSize),
                          controlPoint: CGPoint(x: rect.maxX, y: rect.maxY - calloutSize))

        // bottom (including callout)
        path.addLine(to: CGPoint(x: rect.midX + calloutSize, y: rect.maxY - calloutSize))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.midX - calloutSize, y: rect.maxY - calloutSize))
        path.close()

        fillColor.setFill()
        path.fill()

        strokeColor.setStroke()
        path.lineWidth = lineWidth
        path.stroke()
    

产生:

【讨论】:

哇!这真的是很好的答案!我试图找出解决问题的方法,用 UIBezierPath 设置角落,我自己得到了我想要的。但如果我能找到这个答案,我想我可以更快地得到答案!很好!!【参考方案3】:

而不是用直线开始代码:

path.moveToPoint(CGPoint(x: 300, y: 0))

我改为从弧线开始(右上角):

path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2)  , clockwise: true) //1st rounded corner

通过这样做,我有四个圆角,我只需要在之前的代码末尾添加一条直线:

path.closePath()  

这是代码和截图:

let path = UIBezierPath()
path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2)  , clockwise: true) //1st rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 50), radius:10, startAngle: CGFloat(2 * M_PI / 3), endAngle:CGFloat(M_PI) , clockwise: true)// 2rd rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 10), radius:10, startAngle: CGFloat(M_PI), endAngle:CGFloat(3 * M_PI / 2), clockwise: true)// 3rd rounded corner
// little triangle
path.addLineToPoint(CGPoint(x:240 , y:0))
path.addLineToPoint(CGPoint(x: 245, y: -10))
path.addLineToPoint(CGPoint(x:250, y: 0))
path.addArcWithCenter(CGPoint(x: 290, y: 10), radius: 10, startAngle: CGFloat(3 * M_PI / 2), endAngle: CGFloat(2 * M_PI ), clockwise: true)
path.addLineToPoint(CGPoint(x:300 , y:50))
path.closePath()

【讨论】:

很高兴你找到了它。这实际上是我所描述的。 :D 为了改善这一点,您应该将半径、高度和宽度放入变量中。这样,您只需更改一个值即可使气泡具有任意大小和任意圆角半径。 我应该把 x 和 y 放在变量中吗?但是它们从一行代码到另一行是不同的。你能解释更多吗?谢谢 啊。好吧,我实际上会在可以独立定位的单独 UIView 上绘制它。然后气泡的位置将从 (0, 0) 开始。在这种情况下,尽管您的 x y 值是气泡位置及其大小的函数。右下角是 (x + width, y + height) 等等。【参考方案4】:

Swift 5 与配置变量:

override func draw(_ rect: CGRect) 
    let arrowXOffset: CGFloat = 13
    let cornerRadius: CGFloat = 6
    let arrowHeight: CGFloat = 6

    let mainRect = CGRect(origin: rect.origin, size: CGSize(width: rect.width, height: rect.height - arrowHeight))

    let leftTopPoint = mainRect.origin
    let rightTopPoint = CGPoint(x: mainRect.maxX, y: mainRect.minY)
    let rightBottomPoint = CGPoint(x: mainRect.maxX, y: mainRect.maxY)
    let leftBottomPoint = CGPoint(x: mainRect.minX, y: mainRect.maxY)

    let leftArrowPoint = CGPoint(x: leftBottomPoint.x + arrowXOffset, y: leftBottomPoint.y)
    let centerArrowPoint = CGPoint(x: leftArrowPoint.x + arrowHeight, y: leftArrowPoint.y + arrowHeight)
    let rightArrowPoint = CGPoint(x: leftArrowPoint.x + 2 * arrowHeight, y: leftArrowPoint.y)

    let path = UIBezierPath()
    path.addArc(withCenter: CGPoint(x: rightTopPoint.x - cornerRadius, y: rightTopPoint.y + cornerRadius), radius: cornerRadius,
                startAngle: CGFloat(3 * Double.pi / 2), endAngle: CGFloat(2 * Double.pi), clockwise: true)
    path.addArc(withCenter: CGPoint(x: rightBottomPoint.x - cornerRadius, y: rightBottomPoint.y - cornerRadius), radius: cornerRadius,
                startAngle: 0, endAngle: CGFloat(Double.pi / 2), clockwise: true)

    path.addLine(to: rightArrowPoint)
    path.addLine(to: centerArrowPoint)
    path.addLine(to: leftArrowPoint)

    path.addArc(withCenter: CGPoint(x: leftBottomPoint.x + cornerRadius, y: leftBottomPoint.y - cornerRadius), radius: cornerRadius,
                startAngle: CGFloat(Double.pi / 2), endAngle: CGFloat(Double.pi), clockwise: true)
    path.addArc(withCenter: CGPoint(x: leftTopPoint.x + cornerRadius, y: leftTopPoint.y + cornerRadius), radius: cornerRadius,
                startAngle: CGFloat(Double.pi), endAngle: CGFloat(3 * Double.pi / 2), clockwise: true)


    path.addLine(to: rightTopPoint)
    path.close()

【讨论】:

感谢您的回答!您可以简单地使用 CGFloat.pi 而不是 CGFloat(Double.pi)【参考方案5】:

您无法自动执行此操作。您必须使线条更短,然后使用您想要的圆角半径的圆弧。

所以。不是向 x,y 添加一条线,而是将线添加到 x-radius, y。 然后添加圆弧。然后下一行从 x, y+radius 开始。

【讨论】:

@AziCode 我明天可以。不过现在是凌晨 1 点 30 分。 :-)

以上是关于如何将圆角添加到 UIBezierPath 自定义矩形?的主要内容,如果未能解决你的问题,请参考以下文章

给Button添加特定个数的圆角

UIButton中的UIBezierPath圆角

像在绘图应用程序中一样指定具有角半径的 UIBezierPath 点(例如 Sketch)

使用 UIBezierPath 的带有圆角的 TableView 页眉和页脚

我可以向 UIBezierPath 添加自定义线帽吗?

使用 UIBezierPath 的圆角半径