をサブクラス化し、そのレイヤーにサブレイヤーをUIView
追加し、オーバーライドして形状レイヤーのプロパティを更新することにより、カスタムの循環進行状況ビューを作成しました。CAShapeLayer
drawRect()
path
ビュー@IBDesignable
とprogress
プロパティを作成する@IBInspectable
ことで、Interface Builder でその値を編集し、更新されたベジエ パスをリアルタイムで確認できました。必須ではありませんが、本当にクールです!
次に、パスをアニメーション化することにしました。コードで新しい値を設定するたびに、進行状況を示す円弧が長さ 0 から、達成された円のパーセンテージまで "成長" する必要があります (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
}
そしてレイヤーコード:
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 に反映されますが、パスはレンダリングされません。
何か不足していますか?