CALayer (CAAnimation) 动画属性绘制错误

Posted

技术标签:

【中文标题】CALayer (CAAnimation) 动画属性绘制错误【英文标题】:CALayer (CAAnimation) animatable property drawing bug 【发布时间】:2019-12-04 22:10:20 【问题描述】:

我正在创建带有条形动画的条形图。我在视图下划线层(这是我的自定义BarChartLayer)中绘制它们。这一层负责所有的绘图。

对于动画图表条,我使用名为 procentAnimation 的动画属性。在这个值的动画变化期间,在 draw 方法中,我计算了两个数组(oldValuesnewValues)的值之间的步长。

从 oldValues 到 newValues 的第一个动画效果很好。在动画委​​托中的动画之后,我交换了两个数组的值。

第二个动画应该看起来像反向动画。但是动画开始后,条形图跳到第一个动画开始位置,然后动画像第一个动画一样连续,完成后图表条形图跳回到第二个动画的最终位置。

在调试模式下,我发现,当我添加第二个动画时,属性 oldValuesnewValues 的值保持正确,但是当动画在方法 draw(in:) 中开始时,oldValues 和 @987654329 的值@ 跳回到第一个动画期间的状态。

这是一个错误还是有一些我不明白的机制?如何解决这个问题?

class BarChartLayer: CALayer 

    var oldValues: [CGFloat] = [45, 34, 12, 88, 17, 77, 88, 10] 
        didSet 
            debugPrint("oldValues set")
        
    
    var newValues: [CGFloat] = [92, 72, 64, 32, 28, 15, 11, 33] 
        didSet 
            debugPrint("newValues set")
        
    

    var numberOfBars: CGFloat = 8
    var space: CGFloat = 10

    @objc var procentAnimation : CGFloat = 0
    override class func needsDisplay(forKey key: String) -> Bool 
        if key == #keyPath(procentAnimation) 
            return true
        
        return super.needsDisplay(forKey:key)
    

    func animateBarsChange() 
        let ba = CABasicAnimation(keyPath:#keyPath(BarChartLayer.procentAnimation))
        procentAnimation = 100
        ba.duration = 4.0
        ba.fromValue = 0
        ba.delegate = self
        add(ba, forKey:nil)
    

    func barWidth(rect: CGRect) -> CGFloat 
        return (rect.width - CGFloat(space * (numberOfBars - 1))) / CGFloat(numberOfBars)
    

    func procentRanndomizer(rect: CGRect) -> CGFloat 
        return CGFloat.random(in: 1...100) * rect.height / 100
    

    func makeProcentFor(value: CGFloat, rect: CGRect) -> CGFloat 
        return value * rect.height / 100
    

    func additionalHeight(index: Int) -> CGFloat 
        return (newValues[index] - oldValues[index]) / 100
    

    func makeRandomArr() -> [CGFloat] 
        var arr = [CGFloat]()
        for _ in 0..<8 
            arr.append(CGFloat.random(in: 1...100))
        
        return arr
    

    override func draw(in ctx: CGContext) 
        UIGraphicsPushContext(ctx)
        let con = ctx
        let rect = self.bounds

        con.addRect(rect)
        con.setFillColor(UIColor.gray.cgColor)
        con.fillPath()

        var px:CGFloat = 0
        let bw = barWidth(rect: rect)

        debugPrint("OLD - \(oldValues)")
        debugPrint("NEW - \(newValues)")

        let oldValues = self.oldValues

        for i in 0..<Int(numberOfBars) 
            px = CGFloat(i) * (bw + space)
            let h = oldValues[i] + procentAnimation * additionalHeight(index: i)
            con.addRect(CGRect(x: px, y: 0, width: bw, height: makeProcentFor(value: h, rect: rect)))
        

        con.setFillColor(UIColor.green.cgColor)
        con.fillPath()
        UIGraphicsPopContext()
    


extension BarChartLayer: CAAnimationDelegate 
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) 
        if flag 
            let temp = self.oldValues
            self.oldValues = self.newValues
            self.newValues = temp//makeRandomArr()
            self.procentAnimation = 0
            debugPrint("------------------------------------")
            setNeedsDisplay()
        
    

【问题讨论】:

【参考方案1】:

您应该使用 @NSManaged 标记自定义动画属性,因为 Core Animation 会创建模型层的副本以进行渲染。 @NSManaged 不允许使用默认值。所以你应该改写defaultValue(forKey:)

@NSManaged var procentAnimation : CGFloat

override class func defaultValue(forKey inKey: String) -> Any? 
    return inKey == "procentAnimation" ? 0.0 : super.defaultValue(forKey: inKey)

您也不应该在 CALayer 实现中使用 UIKit 函数。因此,您应该将 UIGraphicsPushContextUIGraphicsPopContext 替换为对应的 Core Graphics:

override func draw(in ctx: CGContext) 
    ctx.saveGState()
    ...
    ctx.restoreGState()

【讨论】:

以上是关于CALayer (CAAnimation) 动画属性绘制错误的主要内容,如果未能解决你的问题,请参考以下文章

CAAnimation - 更改动画最后一帧的属性?

CALayer的动画结束回调?

iOS开发CAAnimation详解

CAAnimation - 显示/隐藏持续时间为 0

带有 timeRange 的 CAAnimation - iOS

CAAnimation 在发布的 CALayer 上运行回调——Mac OS X