【问题标题】:Layer backed NSView with custom CALayer not calling updateLayer?使用自定义 CALayer 不调用 updateLayer 的层支持 NSView?
【发布时间】:2015-05-22 08:20:19
【问题描述】:

我有一个自定义层支持的 NSView 并覆盖了 makeBackingLayer 方法以返回自定义 CALayer 子类。我还重写了 WantUpdateLayer 以返回 true,从而完全选择了类似图层的绘图。

override func makeBackingLayer() -> CALayer {
    return Layer() // my custom class with "drawLayer:" implemented
}

override var wantsUpdateLayer:Bool{
    return true
}

// never called
override func updateLayer() {
    super.updateLayer()
    println("updateLayer after")
    self.layer?.borderWidth += 1
}

一旦我这样做了,我发现当我设置 NSView.needsDisplay = true 时,它会将调用路由到自定义层的 drawInContext: 方法,而不是 updateLayer: 方法。 为什么这样做?在我的示例中,我检查了如果我删除 makeBackingLayer 覆盖,那么我的 updateLayer 会以预期的方式被调用。

我不能完全把我的手指放在它身上,但其他实例指出,当您的 makeBackingLayerreturns a customCALayer` 实际上将您的自定义层托管在父支持层中时。 (纯属猜测)

此外,考虑到 CALayer 的 drawInContext: 更“低级”,这两个绘图路线之间是否会有不同的性能特征?有关该问题的更多详细信息,请参阅此 SO 问题:Layer-backed NSView performance with rendering directly in CALayer.drawInContext:

任何见解将不胜感激。

【问题讨论】:

    标签: cocoa core-animation calayer nsview appkit


    【解决方案1】:

    当您覆盖makeBackingLayer 时,您有责任设置该层,包括设置其delegate: CALayerDelegate。使用该层将delegate 设置为视图就足够了。从那里,您可以实现任何这些委托方法,尽管您可能想要func display(_ layer: CALayer)。有关更多信息,请参阅Core Animation Guide - Providing a Layer's Contents

    updateLayer 的代码路径取决于使用默认层类型的NSView。这是我正在工作的NSView 子类,它在边界更改时重绘:

    /**
     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
        }
    }
    

    请注意,我不需要设置 wantsUpdateLayer 以在我的子类或子类的 layerContentsRedrawPolicy = LayerContentsRedrawPolicy.duringViewResize 上返回 true。这大概是因为两者都依赖于股票层类型,而不是子类。

    以下是我之前的错误答案中的一些更有用的资源:

    Apple's Core Animation Guide

    重写 WantUpdateLayer 并返回 YES 会导致 NSView 类遵循备用渲染路径。视图调用您的 updateLayer 方法,而不是调用 drawRect:,该方法的实现必须将位图直接分配给图层的内容属性。这是 AppKit 期望您直接设置视图层对象的内容的一种情况。

    同样在 NSView.h 中,updateLayer 的文档是

    /* 支持图层的视图:如果视图对 WantUpdateLayer 的响应为“是”,则将调用 updateLayer,而不是 drawRect:。应该使用此方法以获得更好的性能;使用共享图像直接设置 layer.contents 并通知它如何使用 layer.contentsCenter 属性进行拉伸而不是使用 drawRect: 绘制到上下文中会更快。通常,还应该在 init 方法中将 layerContentsRedrawPolicy 设置为适当的值(通常需要 NSViewLayerContentsRedrawOnSetNeedsDisplay)。为了表示图层内容的刷新,然后调用 [view setNeedsDisplay:YES],当图层需要其内容时,将延迟调用 -updateLayer。在此方法期间不应更改几何图形或添加/删除子视图(或图层)。要添加子视图(或图层),请使用 -layout。即使未启用自动布局,-layout 仍将被调用,并且 wantUpdateLayer 返回 YES。 */

    【讨论】:

      【解决方案2】:

      你现在肯定已经修好了,但对于未来的读者,我通过阅读 NSView documentation! 找到了可能的解释!

      如果 canDrawSubviewsIntoLayer 属性设置为 YES,则视图 忽略此方法返回的值。相反,视图总是 使用它的drawRect: 方法来绘制它的内容。

      【讨论】:

        【解决方案3】:

        您是否忘记设置layerContentsRedrawPolicywantsLayer 属性?

        self.wantsLayer = YES;
        self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
        

        【讨论】:

        • 我拥有与您在上面一样的财产。不幸的是,这不是答案。
        猜你喜欢
        • 2018-08-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-20
        • 1970-01-01
        • 2010-11-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多