首页 > 解决方案 > CALayer (CAAnimation) 动画属性绘制错误

问题描述

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

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

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

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

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

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

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()
        }
    }
}

标签: iosswiftdrawingcalayercaanimation

解决方案


您应该使用标记自定义动画属性,@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 函数。所以你应该用他们的Core Graphics对应物替换UIGraphicsPushContext和:UIGraphicsPopContext

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

推荐阅读