Я создал собственное круговое представление прогресса, создав подкласс UIView
, добавив подслой CAShapeLayer
к его слою и переопределив drawRect()
, чтобы обновить свойство path
слоя формы.
Сделав представление @IBDesignable
и свойство progress
@IBInspectable
, я смог отредактировать его значение в Interface Builder и увидеть обновленный путь Безье в реальном времени. Необязательно, но очень круто!
Затем я решил сделать путь анимированным: всякий раз, когда вы устанавливаете новое значение в коде, дуга, указывающая прогресс, должна «расти» от нулевой длины до любого процента круга, достигнутого (вспомните дуги в приложении «Активность» в Apple Watch). ).
Чтобы достичь этого, я заменил свой подслой CAShapeLayer
на пользовательский подкласс CALayer
, который имеет свойство @dynamic
(@NSManaged
), наблюдаемое как ключ для анимации (я реализовал needsDisplayForKey()
, actionForKey()
, drawInContext()
и т. д.).
Мой код View (соответствующие части) выглядит примерно так:
// Triggers path update (animated)
private var progress: CGFloat = 0.0 {
didSet {
updateArcLayer()
}
}
// Programmatic interface:
// (pass false to achieve immediate change)
func setValue(newValue: CGFloat, animated: Bool) {
if animated {
self.progress = newValue
} else {
arcLayer.animates = false
arcLayer.removeAllAnimations()
self.progress = newValue
arcLayer.animates = true
}
}
// Exposed to Interface Builder's inspector:
@IBInspectable var currentValue: CGFloat {
set(newValue) {
setValue(newValue: currentValue, animated: false)
self.setNeedsLayout()
}
get {
return progress
}
}
private func updateArcLayer() {
arcLayer.frame = self.layer.bounds
arcLayer.progress = progress
}
И код layer:
var animates: Bool = true
@NSManaged var progress: CGFloat
override class func needsDisplay(forKey key: String) -> Bool {
if key == "progress" {
return true
}
return super.needsDisplay(forKey: key)
}
override func action(forKey event: String) -> CAAction? {
if event == "progress" && animates == true {
return makeAnimation(forKey: event)
}
return super.action(forKey: event)
}
override func draw(in ctx: CGContext) {
ctx.beginPath()
// Define the arcs...
ctx.closePath()
ctx.setFillColor(fillColor.cgColor)
ctx.drawPath(using: CGPathDrawingMode.fill)
}
private func makeAnimation(forKey event: String) -> CABasicAnimation? {
let animation = CABasicAnimation(keyPath: event)
if let presentationLayer = self.presentation() {
animation.fromValue = presentationLayer.value(forKey: event)
}
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animation.duration = animationDuration
return animation
}
Анимация работает, но теперь я не могу отобразить свои пути в Interface Builder.
Я попытался реализовать свое представление prepareForInterfaceBuilder()
следующим образом:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.topLabel.text = "Hello, Interface Builder!"
updateArcLayer()
}
... и изменение текста метки отражается в Interface Builder, но путь не отображается.
Я что-то упустил?