Search code examples
swiftuikitcore-graphicscore-animationcalayer

CALayer during animation is using default property value instead of what should be the current instance's property value


I've recreated my problem in a xCode Playground that samples the bug I'm running into (showing tends to be better at explaining): https://gist.github.com/Wowserman/c1910523ad85cf34d3e08150b65c7074

My problem is this, my CALayer class renders it's drawing perfectly, in this .gif you can see what it is when it is first rendered (Green with a shadow at the bottom) and to be clear that renders how it should, but as soon as I start animating the shadow height is where the problem is.

I animated the shadowHeight from 12.0 to 0.0 and reverse that animation, and well the animation works except the color property manages to change to the default value (Yellow) without my intent!

You can see in this gif, the layer is first rendered in, Green with the shadow (which is intended), then when the view is touched and the layer animates the shrink of the shadow (intended) but the color changes to the default value (Yellow) during the animation and once it is complete it goes back to the instance's proper color value. The animation should be the color's value at all times throughout the animation.

enter image description here

I checked that the color property is never changed, I have a didSet observer printing every time it is changed, and it is only changed during initialization which is how it should be.

I then checked in the draw(in ctx:) method of CALayer and that's where I saw the color property was different.

Another possible problem I thought could be that the layer drawsAsynchronously is true, but after toggling it I identified it as not the problem.

My Problem Summed Up:

The layer's color property is discreetly changing to the default property value during the animation instead of the value set in initialization causing unwanted rendering.


Solution

  • I figured out the solution for anyone in need. When a CALayer is animated, the CALayer isn't mutated, rather it is duplicated by using CALayer's init(layer:).

    I was reading the documentation on this initializer (enter link description here) and found that inside the override you are suppose to copy layer's custom properties to the new CALayer subclass.

    public override init(layer: Any) {
        super.init(layer: layer)
    
        self.common()
        
        guard let overlay = layer as? OverlayLayer else {
            return
        }
        
        self.color = overlay.color
    }
    

    Here's an update Gist with the fix if you're curious

    https://gist.github.com/Wowserman/ca23d9596f2979effd9cc8fa9e73c893