I have watched numerous tutorials on how to create a custom View in Interface Builder. All are for iOS but MacOS should be similar, no? I have tried a few methods but none are completely successful. The init(coder:)
calls the NIB instantiation (either through Bundle.main.loadNibNamed
or NSNib
which, in turn, calls init(coder:)
and ends up with infinite recursion if I class the main view in my nib as my custom class
If I use a standard class then make file's owner my custom class that works better but still isn't right.
Is there an example that creates a custom control, using AppKit, that works? The closest that I have come displays the custom control but none of the autolayout settings work.
It must be fairly simple but I haven't figured it out yet.
Here is what I have so far:
import Cocoa
@IBDesignable
class MyControl: NSView {
@IBOutlet var customView: NSView! // The top level NSView
@IBOutlet weak var insideButton: NSButton! // The button inside the view
let myName: String
required init?(coder: NSCoder) {
super.init(coder: coder)
if Bundle.main.loadNibNamed("MyControl", owner: self, topLevelObjects: nil) {
addSubview(customView)
}
}
}
A nib based on NSView with contains a centered NSButton. The File's Owner
class is set to MyControl
, the top level view remains as NSView
The Main.storyboard
has a Custom View classed as MyControl
centered with height and width set.
When I view Main.storyboard
it has a frame for the custom view but it is blank.
When I run the application the window that displays is blank.
After much searching and a lot of help from Apple Support I have found that creating and using a custom control is very easy in AppKit. It's just that it is like a key in the lock, unless you get it right you won't get much at all.
I have created a sample project and posted it to GitHub here: https://github.com/ctgreybeard/SwiftCustomControl
It's a small project and I hope I have fully commented it so that someone else can understand it.
The gist of the process is this:
File's Owner
to your new class' name.required init?(coder: coder)
let newNib = NSNib(nibNamed: myName, bundle: Bundle(for: type(of: self)))
where myName is the name of the XIB.newNib.instantiate(withOwner: self, topLevelObjects: nil)
the new NSNibself
. Do this in a for loop over the constraints
property. Alternatively you can simply create the constraints as you know them to be.self.addSubview
for all the old top-level subviews
This is easily done in a for loop over the subviews
array in the old NSView.You're done ... the custom control should now appear correctly in Interface Builder and the app.
Commentary: This, as simple as it is, really shouldn't be necessary. I should be able to simply use my custom class name in the top-level NSView in the XIB and be done with it. The code in the init is simply replacing that top-level NSView with our custom view.