像在绘图应用程序(例如,草图)中一样,指定带有拐角半径的UIBezierPath点 [英] Specifying UIBezierPath points with corner radii like in drawing apps (e.g. Sketch)

查看:45
本文介绍了像在绘图应用程序(例如,草图)中一样,指定带有拐角半径的UIBezierPath点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在诸如Sketch之类的绘图应用程序中,绘制矢量时,可以分别为每个点指定自定义角半径.

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

(左上角和右下角也具有自定义半径.)

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

我如何给 addLine 一个拐角半径?

看来我应该能够使用addCurve或addArc来实现我想要的功能,但是我不确定要提供那些值以获得期望的结果.

解决方案

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

如果您知道如何在两条线之间绘制曲线,则可以对整个形状重复相同的算法.

为使事情更容易理解,请参考下图(代码也将引用这些要点):

目标是找出圆的中心以及开始/结束角度.

首先,您应该

In drawing apps such as Sketch, when you draw a vector, you can specify a custom corner radius for each point individually.

For example, here is a 5 point vector, with the middle point's corner radius set to 17:

(The top left and bottom right points have custom radii as well.)

In Swift, I can draw paths using a UIBezierPath, but when I specify addLine, I'm only given an option to specify a point. I don't have the option to specify a corner radius.

How do I give addLine a corner radius?

It seems like I should be able to use either addCurve or addArc to achieve what I want, but I'm not sure what values to supply to those to get the desired results.

解决方案

Instead of addLine, you must use addArc. The values for addArc are dependent on the previous/next points. Thus any helper function should take in the entire array of points you want to use.

If you can figure out how to draw a curve between two of the lines, the same algorithm could be repeated for the entire shape.

To make things easier to understand, refer to this diagram (the code will reference these points as well):

The goal is to find out the circle's center and the start/end angles.

First, you should grab the CGFloat and CGPoint extensions from here.

Next add these helper functions:

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)
    }
}

Finally, you can now use these functions to render any vector defined in a drawing app easily:

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()
}

Which will produce this exact replica:

这篇关于像在绘图应用程序(例如,草图)中一样,指定带有拐角半径的UIBezierPath点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆