我正在制作一个带有 4 个 UIBezierArc 的 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()
}
这是它目前的样子:
最佳答案
所以你想要这个:
让我们在 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
}
}
有了这个扩展,我们可以像这样创建楔子:
我们可以在 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
结果:
所以现在我们要添加楔形之间的间隙。
考虑这个图表:
在图中,有一个半径为 r
的圆(以原点为中心),以及该圆的弧度为 θ
。当 θ
以弧度为单位时,弧的长度为 θr
。 (这个公式 θr
就是我们使用弧度来测量角度的原因!)
在上面的无间隙方法中,θ
(作为变量innerAngle
和outerAngle
)是.pi/4
。但是现在我们希望角度小于.pi/4
来形成一个缺口。我们希望沿内径的间隙长度等于沿外径的间隙长度。所以我们有一个预定的间隙长度 g
,我们需要为它计算适当的 θ
。
gapless arc length = r π / 4
gapful arc length = θ r = r π / 4 - g / 2
(我们使用g/2
,因为每个楔形的一端有一半间隙,另一端有一半间隙。)
θ r = r π / 4 - g / 2
// Solve for θ by dividing both sides by r:
θ = π / 4 - g / (2 r)
现在我们可以更新扩展中的公式 innerAngle
和 outerAngle
,以创建包含间隙的路径:
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
}
然后我们更新 SimonWedgeView
的 wedgePath
方法来计算间隙长度并将其传递给 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
}
我们得到了想要的结果:
您可以找到完整的 playground 源代码(针对有漏洞的版本)in this gist .
顺便说一下,在 draw 方法生效后,您可能想要检测哪个楔子被点击了。为此,您需要覆盖 SimonWedgeView
中的 point(inside:with:)
方法。我解释一下该怎么做in this answer .
关于ios - 如何扩展 UIBezierPath 弧的外边缘,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53937535/