Итак, вы хотите это:
![wedges with gaps](https://i.stack.imgur.com/0w2ZS.png)
Давайте напишем расширение на 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
}
}
С этим расширением мы можем создавать клины, как это:
![wedge examples](https://i.stack.imgur.com/Pr3Xe.png)
И мы можем использовать это расширение в подклассе 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
Результат:
![wedge views](https://i.stack.imgur.com/3Vqjn.png)
Итак, теперь мы хотим добавить промежутки между клиньями.
Рассмотрим следующую диаграмму:
![arc diagram](https://i.stack.imgur.com/rThPM.png)
На диаграмме есть круг радиуса 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
}
Затем мы обновляем 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
}
И мы получаем желаемый результат:
![wedges with gaps](https://i.stack.imgur.com/0w2ZS.png)
Вы можете найти полный исходный код игровой площадки (для версии с пробелами) в этом списке .
ByКстати, после того, как вы получите метод рисования, вы, вероятно, захотите определить, какой клин был повернут.Для этого вам нужно переопределить метод point(inside:with:)
в SimonWedgeView
.Я объясняю, что делать в этом ответе .