IBInspectable not rendering Layer of NSView

718 Views Asked by At

I want to edit border width and background color of an NSView and made those values IBInspectable:

@IBInspectable var borderWidth: CGFloat{
    set{
        layer?.borderWidth = newValue
    }
    get{
        return layer!.borderWidth
    }
}

@IBInspectable var backgroundColor: NSColor{
    set{
        layer?.backgroundColor = newValue.CGColor
    }
    get{
        return NSColor(CGColor: layer!.backgroundColor)!
    }

}

In the init method, I wrote:

override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)

    wantsLayer = true
    layer?.setNeedsDisplay()
}

When I run the app, changes are shown correctly, but the view doesn't live-render in the interface builder. I don't get any errors or warnings either.

2

There are 2 best solutions below

1
On

i found the solution myself: it seems that the init Method does not get called when the view gets rendered in the Interface builder. As a solution i had to add a global variable which creates a CALayer() when needed:

@IBDesignable class CDPProgressIndicator: NSView {

// creates a CALayer() and sets it to the layer property of the view
var mainLayer: CALayer{
    get{
        if layer == nil{
            layer = CALayer()
        }
        return layer!
    }
}

//referencing to mainLayer ensures, that the CALayer is not nil 
@IBInspectable var borderWidth: CGFloat{
    set{
        mainLayer.borderWidth = newValue
    }
    get{
        return mainLayer.borderWidth
    }
}

@IBInspectable var backgroundColor: NSColor{
    set{
        mainLayer.backgroundColor = newValue.CGColor
    }
    get{
        return NSColor(CGColor: mainLayer.backgroundColor)!
    }

}

and now it finally renders in the interface builder as well.

0
On

The setting of wantsLayer is sufficient to have the layer created when running the app, but it doesn’t work in IB. You have to set the layer explicitly. I’d suggest doing this in prepareForInterfaceBuilder, that way you are using the approved technique, wantsLayer, in your app, but we handle the IB exception, too. For example:

@IBDesignable
class CustomView: NSView {

    @IBInspectable var borderWidth: CGFloat {
        didSet { layer!.borderWidth = borderWidth }
    }

    @IBInspectable var borderColor: NSColor {
        didSet { layer!.borderColor = borderColor.cgColor }
    }

    @IBInspectable var backgroundColor: NSColor {
        didSet { layer!.backgroundColor = backgroundColor.cgColor }
    }

    // needed because IB doesn't don't honor `wantsLayer`
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        layer = CALayer()
        configure()
    }

    override init(frame: NSRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    required init?(coder decoder: NSCoder) {
        super.init(coder: decoder)
        configure()
    }

    private func configure() {
        wantsLayer = true
        ...
    }
}

That handles this IB edge case (bug?), but doesn’t alter the standard flow when running the app.