Code to connect NSbutton to a method to load a NSView from Xib
Here you go. (Made it a second answer, rather than editing my previous one, because the previous one is already long enough. I'm still new here, and I hope this isn't against any rules. Not trying to game the reputation thing :)
The main view controller is still generated from the storyboard.
If this is somewhat verbose, I just wanted to show all the machinery. There are doubtless many other ways to do the same thing.
class ViewController: NSViewController {
// Initializing these objects here eliminates optionals
let stackView = NSStackView(views: [makeView(.purple), makeView(.black)])
let redButton = labeledButton("Red", action: #selector(showRed))
let greenButton = labeledButton("Green", action: #selector(showGreen))
let blueButton = labeledButton("Blue", action: #selector(showBlue))
// Action handlers are objective-c functions. @IBAction implies that
@objc func showRed(_ sender: Any) {
stackView.arrangedSubviews[1].layer?.backgroundColor = NSColor.red.cgColor
}
@objc func showGreen(_ sender: Any) {
stackView.arrangedSubviews[1].layer?.backgroundColor = NSColor.green.cgColor
}
@objc func showBlue(_ sender: Any) {
stackView.arrangedSubviews[1].layer?.backgroundColor = NSColor.blue.cgColor
}
// Helper to make multiple buttons. Static so it can initialize instance properties
class func labeledButton(_ stringValue: String = "", action: Selector) -> NSButton {
let button = NSButton()
button.action = action
button.translatesAutoresizingMaskIntoConstraints = false
button.bezelStyle = NSButton.BezelStyle.rounded
button.title = stringValue
return button
}
// Helper to make multiple views. Also must be static
class func makeView(_ color: NSColor) -> NSView {
let vw = NSView()
vw.translatesAutoresizingMaskIntoConstraints = false
vw.wantsLayer = true
vw.layer?.backgroundColor = color.cgColor
return vw
}
private func createStackView() {
// Configure & add the stack view
stackView.orientation = .horizontal
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
// Pin it to the enclosing view
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Set widths of subviews, and pin them to the stack view
stackView.arrangedSubviews[0].widthAnchor.constraint(equalToConstant: 100).isActive = true
stackView.arrangedSubviews[1].widthAnchor.constraint(greaterThanOrEqualToConstant: 300).isActive = true
stackView.arrangedSubviews[0].topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
stackView.arrangedSubviews[1].topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
stackView.arrangedSubviews[0].bottomAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
stackView.arrangedSubviews[1].bottomAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
// Add the buttons to the left view
stackView.arrangedSubviews[0].addSubview(redButton)
stackView.arrangedSubviews[0].addSubview(greenButton)
stackView.arrangedSubviews[0].addSubview(blueButton)
// Space them vertically, and center them horizontally
redButton.centerXAnchor.constraint(equalTo: stackView.arrangedSubviews[0].centerXAnchor).isActive = true
greenButton.centerXAnchor.constraint(equalTo: stackView.arrangedSubviews[0].centerXAnchor).isActive = true
blueButton.centerXAnchor.constraint(equalTo: stackView.arrangedSubviews[0].centerXAnchor).isActive = true
redButton.topAnchor.constraint(equalTo: stackView.arrangedSubviews[0].topAnchor, constant: 40).isActive = true
greenButton.centerYAnchor.constraint(equalTo: stackView.arrangedSubviews[0].centerYAnchor).isActive = true
blueButton.bottomAnchor.constraint(equalTo: stackView.arrangedSubviews[0].bottomAnchor, constant: -40).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
createStackView()
}
}
Swift 3 Load xib. NSBundle.mainBundle().loadNibNamed return Bool
First of all the method has not been changed in Swift 3.
loadNibNamed(_:owner:topLevelObjects:)
has been introduced in macOS 10.8 and was present in all versions of Swift. However loadNibNamed(nibName:owner:options:)
has been dropped in Swift 3.
The signature of the method is
func loadNibNamed(_ nibName: String,
owner: Any?,
topLevelObjects: AutoreleasingUnsafeMutablePointer<NSArray>?) -> Bool
so you have to create an pointer to get the array of the views on return.
var topLevelObjects = NSArray()
if Bundle.main.loadNibNamed("CardView", owner: self, topLevelObjects: &topLevelObjects) {
let views = (topLevelObjects as Array).filter { $0 is NSView }
return views[0] as! NSView
}
Edit: I updated the answer to filter the NSView
instance reliably.
In Swift 4 the syntax slightly changed and using first(where
is more efficient:
var topLevelObjects : NSArray?
if Bundle.main.loadNibNamed(assistantNib, owner: self, topLevelObjects: &topLevelObjects) {
return topLevelObjects!.first(where: { $0 is NSView }) as? NSView
}
Custom NSView with NSStackView
I have found the answer, you need to initiate properly the view. To do so :
class CustomView: NSView, NSTableViewDelegate, NSTableViewDataSource {
@IBOutlet var viewOutlet: NSView!
@IBOutlet weak var segmentControlOutlet: NSSegmentedControl!
@IBOutlet weak var tableViewOutlet: NSTableView!
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
Bundle.main.loadNibNamed("CustomView", owner: self, topLevelObjects: nil)
addSubview(viewOutlet)
viewOutlet.frame = self.bounds
viewOutlet.autoresizingMask = [.height, .width]
}
required init?(coder: NSCoder) {
super.init(coder: coder)
Bundle.main.loadNibNamed("CustomView", owner: self, topLevelObjects: nil)
addSubview(viewOutlet)
viewOutlet.frame = self.bounds
viewOutlet.autoresizingMask = [.height, .width]
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
}
Please take car that when you load the nib name, "CustomView" is the name of your xib file ;)
How to use custom NSView in Interface Builder?
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:
- In Interface Builder create a XIB and a subclass of NSView. They should be the same name but this is not required.
- For the XIB change the class of
File's Owner
to your new class' name. - Build your new custom control as you want it to be.
- Include an IBOutlet in your class referencing the top-level NSView in the XIB. Also include any other actions or outlets that your control needs.
- Create the initializer
required init?(coder: coder)
- Within that initializer:
- Load the nib using
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 NSNib- Recreate all the existing constraints from the old top-level NSView replacing the old NSView with
self
. Do this in a for loop over theconstraints
property. Alternatively you can simply create the constraints as you know them to be. self.addSubview
for all the old top-levelsubviews
This is easily done in a for loop over thesubviews
array in the old NSView.- Apply the new array of constraints you created above.
- Load the nib using
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.
Load multiple instances of a NSView from Nib
Use the filter
function to get the NSView
instance
func loadView() -> NSView {
var topLevelObjects = NSArray()
Bundle.main.loadNibNamed("CustomView", owner: self, topLevelObjects: &topLevelObjects)
let views = (topLevelObjects as Array).filter { $0 is NSView }
return views[0] as! NSView
}
Related Topics
How to Set Priority on Constraints in Swift
Mfmailcomposeviewcontroller Error [Mc] Filtering Mail Sheet Accounts for Bundle Id
Background Upload with Share Extension
Set Uitextfield Placeholder Color Programmatically
Is There an Kotlin Equivalent 'With' Function in Swift
Why Can't I Use .Reduce() in a One-Liner Swift Closure with a Variadic, Anonymous Argument
Command Failed Due to Signal: Segmentation Fault: 11 While Emitting Ir Sil Function
Swift: Differencebetween a Typealias and an Associatedtype with a Value in a Protocol
Trying to Add a Protocol to a Class Signature in Swift
Menu Items Disabled in MACos Menubar App
Avaudiosinknode with Non-Default, But Still Device-Native Sample Rates
How to Create Apple Watchos5 Complication
Check If a Func Exists in Swift