如何扩展UIBezierPath弧的外边缘

Lil*_*rry 3 xcode ios swift swift4

我正在制作带有4个UIBezierArcs的Simon Says风格的车轮。我不能只制作具有不同颜色和白色部分的单个弧,因为我需要能够识别出按下了哪个弧。

但是,当我将圆弧放置在一个圆中时,内部边缘之间的白色空间小于外部边缘之间的白色空间,并使这些空间看起来像楔形而不是均匀的矩形。

如何调整外弧边缘,使其开始/结束角度长于内弧边缘?

private struct Constants {
    static let width: CGFloat = 115;
    static let height: CGFloat = 230;
}

override func draw(_ rect: CGRect) {
    let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)

    let radius: CGFloat = bounds.height

    let startAngle: CGFloat = 0 + .pi / 44
    let endAngle: CGFloat = .pi / 2 - .pi / 44

    shapePath = UIBezierPath(arcCenter: center,
                            radius: radius/2 - CGFloat(Constants.width/2),
                            startAngle: startAngle,
                            endAngle: endAngle,
                            clockwise: true)

    shapePath.lineWidth = Constants.width / 2
    color.setStroke()
    shapePath.stroke()
    shapePath.close()
}
Run Code Online (Sandbox Code Playgroud)

这是当前的样子:

目前的样子

rob*_*off 7

所以你想要这个:

带间隙的楔子

让我们写一个扩展UIBezierPath,创建一个概述单个楔形的路径。

为了进行预热,首先我们将编写一个函数来创建楔形路径,而在楔形之间不留间隙:

import UIKit
import PlaygroundSupport

// This is useful to remind us that we measure angles in radians, not degrees.
typealias Radians = CGFloat

extension UIBezierPath {

    static func simonWedge(innerRadius: CGFloat, outerRadius: CGFloat, centerAngle: Radians) -> UIBezierPath {
        let innerAngle: Radians = CGFloat.pi / 4
        let outerAngle: Radians = CGFloat.pi / 4
        let path = UIBezierPath()
        path.addArc(withCenter: .zero, radius: innerRadius, startAngle: centerAngle - innerAngle, endAngle: centerAngle + innerAngle, clockwise: true)
        path.addArc(withCenter: .zero, radius: outerRadius, startAngle: centerAngle + outerAngle, endAngle: centerAngle - outerAngle, clockwise: false)
        path.close()
        return path
    }

}
Run Code Online (Sandbox Code Playgroud)

通过此扩展,我们可以创建如下楔块:

楔形例子

我们可以在UIView子类中使用此扩展来绘制楔形:

class SimonWedgeView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder decoder: NSCoder) {
        super.init(coder: decoder)
        commonInit()
    }

    var centerAngle: Radians = 0 { didSet { setNeedsDisplay() } }
    var color: UIColor = #colorLiteral(red: 0.8549019694, green: 0.250980407, blue: 0.4784313738, alpha: 1) { didSet { setNeedsDisplay() } }

    override func draw(_ rect: CGRect) {
        let path = wedgePath()
        color.setFill()
        path.fill()
    }

    private func commonInit() {
        contentMode = .redraw
        backgroundColor = .clear
        isOpaque = false
    }

    private func wedgePath() -> UIBezierPath {
        let bounds = self.bounds
        let outerRadius = min(bounds.size.width, bounds.size.height) / 2
        let innerRadius = outerRadius / 2
        let path = UIBezierPath.simonWedge(innerRadius: innerRadius, outerRadius: outerRadius, centerAngle: centerAngle)
        path.apply(CGAffineTransform(translationX: bounds.midX, y: bounds.midY))
        return path
    }
}

let rootView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
rootView.backgroundColor = .white

func addWedgeView(color: UIColor, angle: Radians) {
    let wedgeView = SimonWedgeView(frame: rootView.bounds)
    wedgeView.color = color
    wedgeView.centerAngle = angle
    rootView.addSubview(wedgeView)
}

addWedgeView(color: #colorLiteral(red: 0.8549019694, green: 0.250980407, blue: 0.4784313738, alpha: 1), angle: 0)
addWedgeView(color: #colorLiteral(red: 0.5843137503, green: 0.8235294223, blue: 0.4196078479, alpha: 1), angle: 0.5 * .pi)
addWedgeView(color: #colorLiteral(red: 0.2588235438, green: 0.7568627596, blue: 0.9686274529, alpha: 1), angle: .pi)
addWedgeView(color: #colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1), angle: 1.5 * .pi)

PlaygroundPage.current.liveView = rootView
Run Code Online (Sandbox Code Playgroud)

结果:

楔形视图

所以现在我们要添加楔形之间的间隙。

考虑下图:

弧形图

在该图中,有一个半径r为圆的圆(以原点为中心),并且该圆的圆弧对着angle ?。弧的长度为?r?以弧度为单位。(此公式,?r就是为什么我们使用弧度来测量角度!)

在上面的无间隙方法中,?(作为变量innerAngleouterAngle)为.pi / 4。但是现在我们希望角度小于.pi / 4形成间隙。我们希望沿着内半径的间隙长度等于沿着外半径的间隙长度。因此,我们有一个预定的间隙长度,g我们需要计算出合适的间隙长度?

gapless arc length = r ? / 4
gapful arc length = ? r = r ? / 4 - g / 2
Run Code Online (Sandbox Code Playgroud)

(我们之所以使用,g / 2是因为每个楔形在一端具有一半的间隙,而在另一端具有一半的间隙。)

? r = r ? / 4 - g / 2
// Solve for ? by dividing both sides by r:
? = ? / 4 - g / (2 r)
Run Code Online (Sandbox Code Playgroud)

现在,我们可以更新公式innerAngleouterAngle在扩展中创建包含缺口的路径:

static func simonWedge(innerRadius: CGFloat, outerRadius: CGFloat, centerAngle: Radians, gap: CGFloat) -> UIBezierPath {
    let innerAngle: Radians = CGFloat.pi / 4 - gap / (2 * innerRadius)
    let outerAngle: Radians = CGFloat.pi / 4 - gap / (2 * outerRadius)
    let path = UIBezierPath()
    path.addArc(withCenter: .zero, radius: innerRadius, startAngle: centerAngle - innerAngle, endAngle: centerAngle + innerAngle, clockwise: true)
    path.addArc(withCenter: .zero, radius: outerRadius, startAngle: centerAngle + outerAngle, endAngle: centerAngle - outerAngle, clockwise: false)
    path.close()
    return path
}
Run Code Online (Sandbox Code Playgroud)

然后,我们更新的计算wedgePath方法SimonWedgeView并将间隙长度传递给该simonWidge方法:

    private func wedgePath() -> UIBezierPath {
        let bounds = self.bounds
        let outerRadius = min(bounds.size.width, bounds.size.height) / 2
        let innerRadius = outerRadius / 2
        let gap = (outerRadius - innerRadius) / 4
        let path = UIBezierPath.simonWedge(innerRadius: innerRadius, outerRadius: outerRadius, centerAngle: centerAngle, gap: gap)
        path.apply(CGAffineTransform(translationX: bounds.midX, y: bounds.midY))
        return path
    }
Run Code Online (Sandbox Code Playgroud)

我们得到了预期的结果:

带间隙的楔子

您可以在此摘要中找到完整的Playground源代码(适用于带间隙的版本)。

顺便说一句,在使draw方法起作用之后,您可能会想要检测出点击了哪个楔形。为此,您需要覆盖中的point(inside:with:)方法SimonWedgeView。我解释这个答案该怎么做。