CALayer.draw(in :)和CALayer.needsDisplay(forKey :)未按预期调用,表示层意外为零

时间:2018-01-21 21:56:23

标签: ios core-animation calayer

我试图用隐式动画属性实现一个图层,我看到一些非常奇怪的行为。这是一个简单的图层,用于演示我的意思:

class CustomLayer: CALayer {

    override init() {
        super.init()
        implcitlyAnimatedProperty = 0.0000
        needsDisplayOnBoundsChange = true
    }

    override init(layer: Any) {
        super.init(layer: layer)
        implcitlyAnimatedProperty = (layer as! CustomLayer).implcitlyAnimatedProperty
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    @NSManaged
    var implcitlyAnimatedProperty: CGFloat

    override func action(forKey event: String) -> CAAction? {
        if event == "implcitlyAnimatedProperty" {
            let action = CABasicAnimation(keyPath: event)
            action.fromValue = presentation()?.value(forKey: event) ?? implcitlyAnimatedProperty
            return action
        } else {
            return super.action(forKey: event)
        }
    }

    override class func needsDisplay(forKey key: String) -> Bool {
        if key == "implcitlyAnimatedProperty" {
            return true
        } else {
            return super.needsDisplay(forKey: key)
        }
    }

    override func draw(in ctx: CGContext) {
        if presentation() == nil {
            print("presentation is nil")
        }
        print(presentation()?.value(forKey: "implcitlyAnimatedProperty") ?? implcitlyAnimatedProperty)
    }
}

我创建了一个CustomLayer的实例,并尝试隐式地为该属性设置动画,如下所示:

        let layer = CustomLayer()
        view.layer.addSublayer(layer) // I'm doing this in a view controller, when a button is pressed
        CATransaction.begin()
        CATransaction.setDisableActions(false)
        CATransaction.setAnimationDuration(10)
        layer.implcitlyAnimatedProperty = 10 // (1)
        layer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100) // (2)
        CATransaction.commit()

我所看到的行为如下:

  • 使用上面编写的代码,第一次调用draw(in:)时,presentation()会返回nil,这会在我的真实应用中进行图层绘制一次是来自模型层的最终值,这是一种不合需要的视觉伪像。否则,一切都按预期工作。
  • 使用(2)注释掉,在创建图层后永远不会调用needsDisplay(forKey:),并且永远不会调用draw(in:)。但是,action(forKey:) 调用。

这些函数未被调用的常见解释是该层具有代表它代表它处理这些调用的委托。但是这个图层的代表在这里没有,所以这不会发生什么。

为什么会发生这种情况,我该如何解决?

1 个答案:

答案 0 :(得分:3)

您的图层未被绘制,因为它具有空边界且不可见。您应该在交易前移动(2)而不是移除它。

您的代码的一些注释:

您不应该编写初始化程序来设置属性。改为覆盖defaultValue(forKey:)

override class func  defaultValue(forKey key: String) -> Any? {
    if key == "implcitlyAnimatedProperty" {
        return 0.0
    }
    else {
        return super.defaultValue(forKey: key)
    }
}

图层属性的设置器具有一些令人惊讶的功能和副作用。例如。当您在事务内部设置属性时,方法action(forKey:)在值被应用于属性之前被称为。因此,您可以简化该行

action.fromValue = presentation()?.value(forKey: event) ?? implcitlyAnimatedProperty

action.fromValue = implcitlyAnimatedProperty

presentation()可能会在nil中返回draw(in:),因为self可能是您的(模型)图层的表示层。检查model()它将返回您创建的图层。

needsDisplay(forKey:)是一个类方法,每个属性只调用一次。如果属性是可动画的,Core Animation仅为所有图层实例决定一次。