Is IBDesignable broken for AppKit in Xcode 9.2 and Swift 4?

760 views Asked by At

Most questions, and answers related to this, are based on older versions of both Xcode and Swift. Additionally, 90 percent of the questions relate to UIKit and drawing custom controls.

I am adding a standard button, that is centered inside a custom control, decorated with IBDesignable.

import Cocoa

@IBDesignable public class ButtonPresetView: NSView {
    public override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        initialControlSetup()
    }

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

    private func initialControlSetup() {
        let button = NSButton(title: "Hello", target: nil, action: nil)
        button.translatesAutoresizingMaskIntoConstraints = false
        addSubview(button)

        // Configure button
        centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
        centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true
    }
}

I add a custom view to the application and set the class property in the Identity Inspector to my custom class (ButtonPresetView).

It should show the button centered on the canvas, but the canvas is blank. Not sure many people use it this way, but it worked gloriously with Swift 3 in Xcode 8.3.

Does anyone else have this problem?

2

There are 2 answers

4
Dave Weston On BEST ANSWER

I was able to get this to work in the latest Xcode by adding the following two lines to the top of the initialControlSetup function:

wantsLayer = true
canDrawSubviewsIntoLayer = true

I think this basically tells the NSView to render in a way that is more similar to how iOS works. If this worked in Xcode 8.3 as you say, it's possible that Apple introduced this regression in Xcode 9 without realizing it.

0
TimTwoToes On

Dave's answer is correct, I just want to make a note on the consequences of this solution.

When canDrawSubviewsIntoLayer is set to true, all its sub views, that did not enable wantsLayer specifically, will render its contents using the layer of the parent view with canDrawSubviewsIntoLayer set to true.

This means sub view animations is disabled, since they lack a backing layer of their own. To prevent this from happening during runtime, you can put canDrawSubviewsIntoLayer = true into the prepareForInterfaceBuilder() function.

On a curious note, Interface Builder does not render the control, if you explicitly set button.wantsLayer = true, which according to the "canDrawSubviewsIntoLayer" documentation, should give the control its own backing layer and not render itself into the parent layer. This is purely speculation, but I'm guessing as an optimisation, Interface Builder only renders the top layers/controls of the content view.