【问题标题】:CALayer with Animated Path Content, Make Inspectable/Designable具有动画路径内容的 CALayer,使可检查/可设计
【发布时间】:2016-12-02 10:41:49
【问题描述】:

我通过子类化UIView,将CAShapeLayer 子层添加到其层并覆盖drawRect() 以更新形状层的path 属性,创建了一个自定义循环进度视图。

通过创建视图@IBDesignableprogress 属性@IBInspectable,我能够在Interface Builder 中编辑其值并实时查看更新后的贝塞尔路径。非必需品,但真的很酷!

接下来,我决定让路径动画化:每当您在代码中设置新值时,指示进度的弧线应该从零长度“增长”到达到圆的百分比(想想 Activity 应用程序中的弧线)在 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 中,但不会呈现路径。

我错过了什么吗?

【问题讨论】:

    标签: ios core-animation calayer cgpath ibinspectable


    【解决方案1】:

    好吧,这不是很好笑吗...原来我的@Inspectable 属性声明有一个非常愚蠢的错误。

    你能认出来吗?

    @IBInspectable var currentValue: CGFloat {
        set(newValue) {
            setValue(newValue: currentValue, animated: false)
            self.setNeedsLayout()
        }
        get {
            return progress
        }
    }
    

    应该是:

    @IBInspectable var currentValue: CGFloat {
        set(newValue) {
            setValue(newValue: newValue, animated: false)
            self.setNeedsLayout()
        }
        get {
            return progress
        }
    }
    

    也就是说,我丢弃了传递的值 (newValue) 并使用当前值 (currentValue) 来设置内部变量。将值(始终为 0.0)“记录”到 Interface Builder(通过我的标签的 text 属性!)给了我一个线索。

    现在它工作正常,不需要drawRect() 等。

    【讨论】:

    • drawRect() 是 Core Animation 存在的祸根。它应该与使用 Core Graphics 的人隔离,并从使用 Core Animation 的各个方面受到打击。干得好!
    猜你喜欢
    • 2012-07-12
    • 2023-03-25
    • 1970-01-01
    • 2019-01-16
    • 2015-09-05
    • 1970-01-01
    • 2013-10-06
    • 2015-04-21
    • 1970-01-01
    相关资源
    最近更新 更多