CALayer Subclass redraw understanding issue

Hello, I created a subclass of CALayer. But the Software isn't behaving as I expect it, maybe someone can explain to me what I am misunderstanding.

The subclass inherits a function changecolor() which gets called just for test 1s after start within a timer callback. The function currently just sets the background color of the layer to a different value. If I do this, I get the following error:

Test3/ViewController.swift:62: Fatal error: Use of unimplemented initializer 'init(layer:)' for class 'Test3.timeRuler'

I have to implement

override init(layer: Any) {

            super.init(layer: layer)

        }

to get it working. Then the Layers color is updated as expected. BUT WHAT I EXPECTED:

I expected that a change in background color does not need a reinitialization of the layer, I expected that the change first does nothing, and the layer gets redrawn if I set the layers  setNeedsDisplay().

The draw and display functions are never called somehow. They are however called if I call  setNeedsDisplay() but the color changes anyway if override init(layer: Any) is defined.

What am I missing here?

class timeRuler :CALayer{

        override init() {

            super.init();

            frame = NSRect(x: 0, y: 0, width: 300, height: 300);

            backgroundColor=NSColor.blue.cgColor;

        }

        func changecolor(){

            

          backgroundColor=NSColor.purple.cgColor;
          //setNeedsDisplay()

        }

      /*  override init(layer: Any) {

            super.init(layer: layer)

        }*/

        override func draw(in ctx: CGContext) {

            super.draw(in: ctx);

            print("layer draw update");

        }

        override func display() {

            //backgroundColor=NSColor.blue.cgColor;

            super.display();

            print("layer update");

        }

        required init?(coder: NSCoder) {

            fatalError("init(coder:) has not been implemented")

        }

    }

Replies

Hi darkmaterial,

I'm not answering you question directly, cause I don't remember exactly what you are missing. but, I want to write some info I wrote for my self next time I'm going to deal with CALayers and AppKit hope it helps, read and check it, good chance the answer is there :

Difference between uiview-layer and nsview layer

https://stackoverflow.com/questions/9551992/how-to-add-a-calayer-to-an-nsview-on-mac-os-x

https://stackoverflow.com/questions/15966219/calayer-drawrect

Very important slide show of WWDC 2012 about NSView layers and drawing

https://docs.huihoo.com/apple/wwdc/2012/session_217__layerbacked_views_appkit__core_animation.pdf

https://www.objc.io/issues/14-mac/appkit-for-uikit-developers/

By default, AppKit views are not backed by Core Animation layers; layer-backing support has been integrated into AppKit retroactively. But while you never have to worry about this with UIKit, with AppKit there are decisions to make. AppKit differentiates between layer-backed and layer-hosting views

If you want to interact with the layer in such ways, then you have to go one step further. Overriding NSView‘s wantsUpdateLayer method to return YES enables you to change the layer’s properties. If you do this though, AppKit will no longer call the view’s drawRect: method. Instead, updateLayer will be called during the view update cycle, and this is where you can modify the layer.

To make appKit layers works like UIKit layers wantsLayer = YES wantsUpdateLayer return  YES

And now - (void)updateLayer will be called instead of drawRect:

Make sure layerContentsRedrawPolicy property to NSViewLayerContentsRedrawOnSetNeedsDisplay

This way, you have control over when the layer contents need to be redrawn. A frame change will not automatically trigger a redraw anymore; you are now responsible for triggering it by calling -setNeedsDisplay:


Again all the above is note I have for my self, incase I want to do something with CALayers ( rarely). I hope this help you.

Good luck

This is super old thread, but since I found it while searching for the answer, I will answer it.

From the docs about init(layer: Any):

This initializer is used by CoreAnimation to create shadow copies of layers, e.g. for use as presentation layers. Subclasses can override this method to copy their instance variables into the presentation layer (subclasses should call the superclass afterwards). Calling this method in any other situation will result in undefined behavior.

So basically that method gets called when CoreAnimation decides that it needs a new copy of your layer. Changing background color triggers this. On iOS this method got triggered when I change strokeColor. If you have some custom vars on your subclass, you need to copy the values in this method (the layer argument is your old layer so you need to typecast it and then copy the custom params before calling super).