swiftmacoscalayerappkitpresentation-layer

When does a CALayer have a presentationLayer?


I was working with CALayers and I want to retrieve some information about the state of a sublayer of my layer while an animation is running. Now I know I can retrieve the information from the sublayer's presentation layer by calling mySublayer.presentation() and accessing the returned layer's properties.

However the return value is an optional value and does actually return nil relatively often. In my particular case the call happens inside of the layoutSublayers() of my layer (the superlayer of the layer who's information I want).

That's why I was wondering: When does a layer have a presentation layer and when does it not have one? Unfortunately I could't really find anything on this in Apple's documentation.

Thanks a lot!


Solution

  • When does a layer have a presentation layer

    When it's part of the render tree. A layer is always rendered onscreen as its presentation layer.

    The reason you're getting nil is that you're looking at the presentation layer from inside an early layoutSublayers(). That is exactly when the layer does not have a presentation layer; we're in the middle of configuring the render tree for the first time.

    Here's some testing code that will show you what's going on:

    func delay(_ delay:Double, closure:@escaping ()->()) {
        let when = DispatchTime.now() + delay
        DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
    }
    class MyLayer : CALayer {
        override func layoutSublayers() {
            print("here1")
            if let subs = self.sublayers {
                for lay in subs {
                    print(lay.presentation() as Any)
                }
            }
            super.layoutSublayers()
            print("here2")
            if let subs = self.sublayers {
                for lay in subs {
                    print(lay.presentation() as Any)
                }
            }
        }
    }
    class MyView : UIView {
        override class var layerClass: AnyClass { return MyLayer.self }
    }
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            let lay = CALayer()
            lay.backgroundColor = UIColor.black.cgColor
            lay.frame = CGRect(x: 10, y: 10, width: 10, height: 10)
            self.view.layer.addSublayer(lay)
            print(lay.presentation() as Any)
            delay(1) {
                print(lay.presentation() as Any)
                print("let's try rerendering")
                self.view.setNeedsLayout()
                self.view.layoutIfNeeded()
            }
        }
    }
    

    Set the view controller's view to be a MyView in the storyboard, and launch. We get nil repeatedly until we reach the delay call. Now the layer is part of the render tree, so now and on the next round of layout, the layer has a presentation layer.

    Another interesting thing (left as an exercise for the reader) is to animate the layer. The layer acquires a different presentation layer during animation, and yet another presentation layer when the animation is over. It's very important to keep this in mind when working this closely with a layer; its presentation layer is not a constant single object — and it is copied from the original layer each time it is created anew. (Boy, was I confused before I realized that.)