12

For a CALayer,

which is animating,

class Test: CAGradientLayer {

    override func draw(in ctx: CGContext) {

        super.draw(in: ctx)
        startPoint = ....
    }

*** Terminating app due to uncaught exception 'CALayerReadOnly', reason: 'attempting to modify read-only layer

It appears to be impossible to change one of the ordinary animatable properties, inside the draw#inContext call.

So for example:

It's easy and simple to have an animatable custom property of your own, and then draw something based on that. Here's some code for a .progress property,

https://stackoverflow.com/a/37470079/294884

while animating your .progress property, it would be easy to imagine wanting to set other properties of the layer, using some formula based on the value of .progress each frame.

However, you can not do it in the draw#in function - how to do it ?

Fattie
  • 30,632
  • 54
  • 336
  • 607
  • Massive bounty here! – Fattie Dec 15 '17 at 14:28
  • 1
    Just dont do it inside the draw function. The animation stack makes copies of your layer for animation and these are read only. Why dont you use computed properties for example? Or local variables? – Sulthan Dec 15 '17 at 14:31
  • "Just dont do it inside the draw function" hi ... but where else would you do it? Say you are animating the rotation from 0 to 90. At the same time, you want the alpha to go from 1 to 0, matching. It's completely natural that one would "control the other". – Fattie Dec 15 '17 at 15:24
  • "Why dont you use computed properties for example? Or local variables?" that's my very question, you can't - try it ! – Fattie Dec 15 '17 at 15:24
  • Consider this similar issue: https://stackoverflow.com/questions/47164762 how to, very simply, animate animatable properties from another property - so that you can do "a few at once". Maybe it's just impossible. – Fattie Dec 15 '17 at 15:31
  • If you are using a custom animated property, it means you want to do the drawing by yourself. That means you can control all other drawing properties by yourself. If you want to change alpha, just add changed alpha to your drawing. Don't draw with `UIColor.blue`, draw with `UIColor.blue.withAlphaComponent(alpha)` or directly set `CGContext.setAlpha` etc. If you want to change an external property, e.g. `layer.alpha`, you cannot do that from the draw function itself. You have to change it when starting the animation. That's the whole concept of `CAAnimationGroup` and `CATransaction`. – Sulthan Dec 15 '17 at 16:12
  • I understand, BUT it's the most natural thing in the world you would want to change an *existing* ("Apple") animatable property, in relation to something you are animating. Example, I have a custom property ".bend" which runs from 0 to 1 and it bends the object. However, that property wants to change the (say) background color or something, in a relationship with the .bend value. – Fattie Dec 15 '17 at 16:19
  • That's what you would do in a custom *setter* of that value. Or you would just draw the fill color by yourself inside the `draw` function. Also note that the `backgroundColor` is drawn before you `draw` function is even called. – Sulthan Dec 15 '17 at 16:23
  • (right, .opacity is a better example ...) – Fattie Dec 15 '17 at 16:30
  • **That's what you would do in a custom setter of that value** - right, man!! **You'd think that.** Please try it !! Indeed, this is the question: https://stackoverflow.com/questions/47164762 – Fattie Dec 15 '17 at 16:31
  • I have written a lot of custom layers myself back in Obj-C. I will try to come with an example when I have some more time. – Sulthan Dec 15 '17 at 16:38
  • OK. To be clear **I am totally familiar with how to draw layers**. Just as you explain above ("blue" example) I can trivially "draw it myself" from scratch. But I want to know how to affect other (existing) properties, while so. – Fattie Dec 15 '17 at 16:44

1 Answers1

6

When CoreAnimation performs animations, it creates shadow copies of layer, and each copy will be rendered in a different frame. Copies are created by -initWithLayer:. Copies created by this method are read-only. That's why You are geting readonly exception.

You can override this method to create own copies of required properties. For example:

    override init(layer: Any) {
    super.init(layer: layer)
    // Check class
    if let myLayer = layer as? CircleProgressLayer {
        // Copy the value
        startPoint = myLayer.startPoint
    }
}

Instead of setting self.startPoint, you should write self.modelLayer.startPoint = ... because all the presentation copies share the same model.

Note that you must do this also when you read the variable, not only when you set it. For completeness, one should mention as well the property presentation, that instead returns the current (copy of the) layer being displayed.

Refered to Apple Documentation

Andriy
  • 2,727
  • 2
  • 18
  • 29
HereTrix
  • 1,285
  • 7
  • 13
  • unfortunately I just did not yet have time to try out this important answer, which arrived late. But since it is information packed, I am clicking the bounty, so the bounty does not go to waste! Thanks! – Fattie Dec 22 '17 at 14:40
  • The key point is to set properties on `self.modelLayer` instead of `self`. So, if you want to update `backgroundColor` in `drawInContext:` while animating, you should assign to `self.modelLayer.backgroundColor`. Assigning to `self.backgroundColor` or `self.presentationLayer.backgroundColor` gives error as explained in the question. – LGP May 16 '18 at 13:39