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

Posted

技术标签:

【中文标题】像在绘图应用程序中一样指定具有角半径的 UIBezierPath 点(例如 Sketch)【英文标题】:Specifying UIBezierPath points with corner radii like in drawing apps (e.g. Sketch) 【发布时间】:2020-02-03 08:07:29 【问题描述】:

在 Sketch 等绘图应用中,当您绘制矢量时,您可以为每个点单独指定一个自定义圆角半径。

例如,这里是一个 5 点向量,中间点的圆角半径设置为 17:

(左上角和右下角的点也有自定义半径。)

在 Swift 中,我可以使用 UIBezierPath 绘制路径,但是当我指定 addLine 时,我只能选择指定一个点。我没有指定圆角半径的选项。

如何给addLine 一个圆角半径?

似乎我应该能够使用 addCurve 或 addArc 来实现我想要的,但我不确定要为那些提供什么值来获得所需的结果。

【问题讨论】:

【参考方案1】:

您必须使用addArc,而不是addLine。 addArc 的值取决于上一个/下一个点。因此,任何辅助函数都应该包含您要使用的整个点数组。

如果您能弄清楚如何在两条线之间绘制曲线,则可以对整个形状重复相同的算法。

为了让事情更容易理解,请参考此图(代码也会参考这些点):

目标是找出圆的中心和开始/结束角度。

首先,你应该grab the CGFloat and CGPoint extensions from here。

接下来添加这些辅助函数:

extension Collection where Index == Int 
    func items(at index: Index) -> (previous: Element, current: Element, next: Element) 
        precondition(count > 2)
        let previous = self[index == 0 ? count - 1 : index - 1]
        let current = self[index]
        let next = self[(index + 1) % count]
        return (previous, current, next)
    


/// Returns ∠abc (i.e. clockwise degrees from ba to bc)
//
//  b - - - a
//   \
//    \
//     \
//      c
//
func angleBetween3Points(_ a: CGPoint, _ b: CGPoint, _ c: CGPoint) -> CGFloat 
    let xbaAngle = (a - b).angle 
    let xbcAngle = (c - b).angle // if you were to put point b at the origin, `xbc` refers to the angle formed from the x-axis to the bc line (clockwise)
    let abcAngle = xbcAngle - xbaAngle
    return CGPoint(angle: abcAngle).angle // normalize angle between -π to π


func arcInfo(
    previous: CGPoint,
    current: CGPoint,
    next: CGPoint,
    radius: CGFloat)
    -> (center: CGPoint, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)

    let a = previous
    let b = current
    let bCornerRadius: CGFloat = radius
    let c = next

    let abcAngle: CGFloat = angleBetween3Points(a, b, c)
    let xbaAngle = (a - b).angle
    let abeAngle = abcAngle / 2

    let deLength: CGFloat = bCornerRadius
    let bdLength = bCornerRadius / tan(abeAngle)
    let beLength = sqrt(deLength*deLength + bdLength*bdLength)

    let beVector: CGPoint = CGPoint(angle: abcAngle/2 + xbaAngle)

    let e: CGPoint = b + beVector * beLength

    let xebAngle = (b - e).angle
    let bedAngle = (π/2 - abs(abeAngle)) * abeAngle.sign() * -1

    return (
        center: e,
        startAngle: xebAngle - bedAngle,
        endAngle: xebAngle + bedAngle,
        clockwise: abeAngle < 0)


func addArcs(to path: UIBezierPath, pointsAndRadii: [(point: CGPoint, radius: CGFloat)]) 
    precondition(pointsAndRadii.count > 2)

    for i in 0..<pointsAndRadii.count 
        let (previous, current, next) = pointsAndRadii.items(at: i)
        let (center, startAngle, endAngle, clockwise) = arcInfo(
            previous: previous.point,
            current: current.point,
            next: next.point,
            radius: current.radius)

        path.addArc(withCenter: center, radius: current.radius, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise)
    


最后,您现在可以使用这些函数轻松渲染绘图应用程序中定义的任何矢量:

override func draw(_ rect: CGRect) 
    let grayPath = UIBezierPath()
    addArcs(
        to: grayPath,
        pointsAndRadii: [
            (point: CGPoint(x: 100, y: 203), radius: 0),
            (point: CGPoint(x: 100, y: 138.62), radius: 33),
            (point: CGPoint(x: 173.78, y: 100), radius: 0),
            (point: CGPoint(x: 139.14, y: 172.51), radius: 17),
            (point: CGPoint(x: 231, y: 203), radius: 3),
        ])
    grayPath.close()
    grayPath.lineWidth = 5
    UIColor.gray.setStroke()
    grayPath.stroke()


这将产生这个精确的副本:

【讨论】:

ty,你让我度过了安全的一天:*,如果你有兴趣,你可以回答我的问题 - ***.com/questions/65970406/…

以上是关于像在绘图应用程序中一样指定具有角半径的 UIBezierPath 点(例如 Sketch)的主要内容,如果未能解决你的问题,请参考以下文章

Swift中具有角半径的集合视图

具有角半径的 WPF 插入阴影

如何为 CALayer 的角半径设置动画

具有透明背景的颤动角半径

具有角半径和阴影的 SWIFT UITableViewCell

iOS6 向具有角半径的容器 UIView 添加阴影