How do I correctly load an object that is a subclass of NSView using a Xib?
I want it to be loaded dynamically not from the beginning so I made a MyView.Xib From MyD
Note that a nib file contains:
MyView
;NSNib
Let’s consider that your nib file has only one top-level object whose class is MyView
, and the file’s owner class is MyAppDelegate
. In that case, considering that the nib file is loaded in an instance method of MyAppDelegate
:
NSNib *nib = [[[NSNib alloc] initWithNibNamed:@"MyView" bundle:nil] autorelease];
NSArray *topLevelObjects;
if (! [nib instantiateWithOwner:self topLevelObjects:&topLevelObjects]) // error
MyView *myView = nil;
for (id topLevelObject in topLevelObjects) {
if ([topLevelObject isKindOfClass:[MyView class]) {
myView = topLevelObject;
break;
}
}
// At this point myView is either nil or points to an instance
// of MyView that is owned by this code
It looks like the first argument of -[NSNib instantiateNibWithOwner:topLevelObjects:]
can be nil
so you wouldn’t have to specify an owner since it seems that you aren’t interested in having one:
if (! [nib instantiateWithOwner:nil topLevelObjects:&topLevelObjects]) // error
Although this works, I wouldn’t rely on it since it’s not documented.
Note that you have ownership of the top level objects, hence you are responsible for releasing them when they’re no longer needed.
NSViewController
Cocoa provides NSViewController
to manage views; usually, views loaded from a nib file. You should create a subclass of NSViewController
containing whichever outlets are needed. When editing the nib file, set the view
outlet and whichever other outlets you may have defined. You should also set the nib file’s owner so that its class is this subclass of NSViewController
. Then:
MyViewController *myViewController = [[MyViewController alloc] initWithNibName:@"MyView" bundle:nil];
NSViewController
automatically loads the nib file and sets the corresponding outlets when you send it -view
. Alternatively, you can force it to load the nib file by sending it -loadView
.
class CustomView: NSView {
@IBOutlet weak var view: NSView!
@IBOutlet weak var textField: NSTextField!
required init(coder: NSCoder) {
super.init(coder: coder)!
let frameworkBundle = Bundle(for: classForCoder)
assert(frameworkBundle.loadNibNamed("CustomView", owner: self, topLevelObjects: nil))
addSubview(view)
}
}
You can refer to this category
https://github.com/peterpaulis/NSView-NibLoading-/tree/master
+ (NSView *)loadWithNibNamed:(NSString *)nibNamed owner:(id)owner class:(Class)loadClass {
NSNib * nib = [[NSNib alloc] initWithNibNamed:nibNamed bundle:nil];
NSArray * objects;
if (![nib instantiateWithOwner:owner topLevelObjects:&objects]) {
NSLog(@"Couldn't load nib named %@", nibNamed);
return nil;
}
for (id object in objects) {
if ([object isKindOfClass:loadClass]) {
return object;
}
}
return nil;
}
Here is a way to write the NSView subclass so the view itself comes fully from a separate xib and has everything set up correctly. It relies on the fact that init
can change the value of self
.
Create a new 'view' xib using Xcode. Set the File Owner's class to NSViewController, and set its view
outlet to your target view. Set the class of the target view to your NSView subclass. Layout your view, connect outlets etc.
Now, in your NSView subclass, implement the designated initializers:
- (id)initWithFrame:(NSRect)frameRect
{
NSViewController *viewController = [[NSViewController alloc] init];
[[NSBundle mainBundle] loadNibNamed:@"TheNib" owner:viewController topLevelObjects:NULL];
id viewFromXib = viewController.view;
viewFromXib.frame = frameRect;
self = viewFromXib;
return self;
}
And the same with initWithCoder:
so that it will also work when using your NSView subclass in an another xib.
I found the answer by @Bavarious very useful, but it still needed some squirrelling on my part to make it work for my use case - load one of several xib's view definitions into an existing "container" view. Here's my workflow:
1) In the .xib, create the view in IB as expected.
2) DO NOT add an object for the new view's viewController, but rather
3) Set the .xib's File's Owner to new view's viewController
4) Connect any outlets & actions to File's Owner, and, specifically, .view
5) Call loadInspector (see below).
enum InspectorType {
case None, ItemEditor, HTMLEditor
var vc: NSViewController? {
switch self {
case .ItemEditor:
return ItemEditorViewController(nibName: "ItemEditor", bundle: nil)
case .HTMLEditor:
return HTMLEditorViewController(nibName: "HTMLEditor", bundle: nil)
case .None:
return nil
}
}
}
class InspectorContainerView: NSView {
func loadInspector(inspector: InspectorType) -> NSViewController? {
self.subviews = [] // Get rid of existing subviews.
if let vc = inspector.vc {
vc.loadView()
self.addSubview(vc.view)
return vc
}
Swift.print("VC NOT loaded from \(inspector)")
return nil
}
}