簡體   English   中英

像在繪圖應用程序(例如 Sketch)中一樣指定帶有角半徑的 UIBezierPath 點

[英]Specifying UIBezierPath points with corner radii like in drawing apps (e.g. Sketch)

在諸如 Sketch 之類的繪圖應用程序中,當您繪制矢量時,您可以為每個點單獨指定一個自定義圓角半徑。

例如,這是一個 5 點向量,中間點的角半徑設置為 17:

在此處輸入圖片說明

(左上角和右下角的點也有自定義半徑。)

在 Swift 中,我可以使用 UIBezierPath 繪制路徑,但是當我指定addLine ,我只能選擇指定一個點。 我沒有指定拐角半徑的選項。

如何給addLine一個圓角半徑?

似乎我應該能夠使用 addCurve 或 addArc 來實現我想要的,但我不確定要提供給那些值以獲得所需的結果。

您必須使用addArc而不是addArc addArc 的值取決於上一個/下一個點。 因此,任何輔助函數都應該包含您要使用的整個點數組。

如果您能弄清楚如何在兩條線之間繪制曲線,則可以對整個形狀重復相同的算法。

為了讓事情更容易理解,請參考這張圖(代碼也會參考這些點):

在此處輸入圖片說明

目標是找出圓的中心和開始/結束角度。

首先,您應該 從這里獲取 CGFloat 和 CGPoint 擴展

接下來添加這些輔助函數:

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

這將產生這個精確的副本:

在此處輸入圖片說明

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM