Layer backed NSView with custom CALayer not calling updateLayer?

后端 未结 3 2247
别那么骄傲
别那么骄傲 2021-02-13 14:29

I have a custom layer-backed NSView and have overidden the makeBackingLayer method to return a custom CALayer subclass. I have also overriden wantsUpdateLayer to return true the

相关标签:
3条回答
  • 2021-02-13 14:51

    Surely you fixed it by now, but for future readers I found a possible explanation by reading the NSView documentation!

    If the canDrawSubviewsIntoLayer property is set to YES, the view ignores the value returned by this method. Instead, the view always uses its drawRect: method to draw its content.

    0 讨论(0)
  • 2021-02-13 15:04

    When you override makeBackingLayer, it becomes your responsibility to set up that layer, including setting its delegate: CALayerDelegate. It's enough to set the delegate to the view using that layer. From there, you can implement any of those delegate methods, though you probably want func display(_ layer: CALayer). See Core Animation Guide - Providing a Layer's Contents for more on this.

    The code path for updateLayer depends on the NSView using the default layer type. Here is my working NSView subclass that redraws on bounds change:

    /**
     A view backed by a CAShapeLayer that can inscribe an image inside the
     shape layer's circular path
     */  
    @IBDesignable final class InscribedImageView: NSView, CALayerDelegate {
    
        @IBInspectable private var image: NSImage?
        @IBInspectable private var color: NSColor = .clear
    
        // swiftlint:disable:next force_cast
        private var shapeLayer: CAShapeLayer { return self.layer as! CAShapeLayer }
    
        override init(frame frameRect: NSRect) {
            super.init(frame: frameRect)
            configureViewSetting()
        }
    
        required init?(coder decoder: NSCoder) {
            super.init(coder: decoder)
            configureViewSetting()
        }
    
        private func configureViewSetting() {
            wantsLayer = true
        }
    
        override func makeBackingLayer() -> CALayer {
            let layer = CAShapeLayer()
            layer.delegate = self
            layer.needsDisplayOnBoundsChange = true
            return layer
        }
    
        func display(_ layer: CALayer) {
            inscribe(image, in: color)
        }
    
        override func prepareForInterfaceBuilder() {
            super.prepareForInterfaceBuilder()
            inscribe(image, in: color)
        }
    
        func inscribe(_ image: NSImage?, in color: NSColor) {
            self.color = color
            defineCircle(color: color)
            subviews.forEach { $0.removeFromSuperview() }
            if let image = image {
                self.image = image
                let imageView = NSImageView(image: image)
                imageView.imageScaling = .scaleProportionallyUpOrDown
                imageView.frame = bounds
                addSubview(imageView)
            }
        }
    
        private func defineCircle(color: NSColor) {
            shapeLayer.path = CGPath(ellipseIn: shapeLayer.bounds, transform: nil)
            shapeLayer.fillColor = color.cgColor
            shapeLayer.strokeColor = color.cgColor
        }
    }
    

    Note that I didn't need to set wantsUpdateLayer to return true on my subclass OR the subclass's layerContentsRedrawPolicy = LayerContentsRedrawPolicy.duringViewResize. Presumably this is because both rely on the stock layer type, not a subclass.

    Here are some more useful resources from my previous, incorrect answer:

    Apple's Core Animation Guide

    Overriding wantsUpdateLayer and returning YES causes the NSView class to follow an alternate rendering path. Instead of calling drawRect:, the view calls your updateLayer method, the implementation of which must assign a bitmap directly to the layer’s contents property. This is the one scenario where AppKit expects you to set the contents of a view’s layer object directly.

    Also in NSView.h, the docs for updateLayer are

    /* Layer Backed Views: If the view responds YES to wantsUpdateLayer, then updateLayer will be called as opposed to drawRect:. This method should be used for better performance; it is faster to directly set the layer.contents with a shared image and inform it how to stretch with the layer.contentsCenter property instead of drawing into a context with drawRect:. In general, one should also set the layerContentsRedrawPolicy to an appropriate value in the init method (frequently NSViewLayerContentsRedrawOnSetNeedsDisplay is desired). To signal a refresh of the layer contents, one will then call [view setNeedsDisplay:YES], and -updateLayer will be lazily called when the layer needs its contents. One should not alter geometry or add/remove subviews (or layers) during this method. To add subviews (or layers) use -layout. -layout will still be called even if autolayout is not enabled, and wantsUpdateLayer returns YES. */

    0 讨论(0)
  • 2021-02-13 15:05

    Did you forget to set the layerContentsRedrawPolicy and wantsLayer properties?

    self.wantsLayer = YES;
    self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
    
    0 讨论(0)
提交回复
热议问题