1

をサブクラス化し、そのレイヤーにサブレイヤーをUIView追加し、オーバーライドして形状レイヤーのプロパティを更新することにより、カスタムの循環進行状況ビューを作成しました。CAShapeLayerdrawRect()path

ビュー@IBDesignableprogressプロパティを作成する@IBInspectableことで、Interface Builder でその値を編集し、更新されたベジエ パスをリアルタイムで確認できました。必須ではありませんが、本当にクールです!

次に、パスをアニメーション化することにしました。コードで新しい値を設定するたびに、進行状況を示す円弧が長さ 0 から、達成された円のパーセンテージまで "成長" する必要があります (Apple Watch のアクティビティ アプリの円弧を考えてみてください)。 )。

これを実現するために、アニメーションのキーとして観測される ( ) プロパティを持つカスタム サブクラスによってサブレイヤーを交換しました ( 、などを実装しCAShapeLayerましCALayerた) 。@dynamic@NSManagedneedsDisplayForKey()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 に反映されますが、パスはレンダリングされません。

何か不足していますか?

4

1 に答える 1