NSWindowController object linkage in Interface builder - cocoa

I created a NSWindow xib file that I want to open on click of a button in another window.
Now, to control the behavior of the NSWindow, I dragged an object from Library in xib and changed it to subclass of NSWindowController (i.e. ListingWindowController) that I defined in XCode.
Similarly I also created a subclass of NSViewController (i.e. ListingViewController) to manage the NSView inside the NSWindow. To do this, I dragged NSViewController from Library in xib and changed its class to ListingViewController.
#class ListingViewController;
#interface ListingWindowController : NSWindowController {
IBOutlet ListingViewController *listingVC;
}
#property (nonatomic, retain) IBOutlet ListingViewController *listingVC;
#end
I connected window and listingVC of my window controller in IB.
Now to invoke this window on click of a button in my launch (first) window, I create the window controller using initWithWindowNibName like this..
- (IBAction) pushConnect:(id)sender {
NSLog(#"Connect pushed.");
if (wc == nil) {
wc = [[ListingWindowController alloc] initWithWindowNibName:#"ListingWindow" owner:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(closeWindow:) name:NSWindowWillCloseNotification object:nil];
[wc showWindow:sender];
}
}
The problem is that despite all the bindings done in IB for the view controllers of upcoming window/view, the window and listingVC comes out to be (null), (null) even after the new window has loaded (below code).
- (void)windowDidLoad {
[super windowDidLoad];
NSLog(#"windowDidLoad = %#, %#", self.window, self.listingVC);
}
Please help why the connections are not working. I'm banging my head against this problem for quite a while now.
PS: I'm coming from iOS programming background. So, I'm assuming the Mac's window/view controller behave similar to iOS UIViewControllers.
TIA..

Note that:
wc = [[ListingWindowController alloc] initWithWindowNibName:#"ListingWindow" owner:self];
means that self (it’s not clear what self is from your question) is the owner of ListingWindow.nib. This means that self is the one who keeps outlets to objects in that nib file, and self is responsible for releasing the top-level objects in the nib file. This also means that you’re creating an instance of ListingWindowController in your code and another instance inside your nib file since you’ve dragged an object of class ListingWindowController onto the nib file.
This is not how it’s supposed to be.
In the vast majority of cases, a window (view) controller loads a nib file and becomes its owner. It has a window (view) outlet that must be linked to a top-level window (view) in the nib file. Being the nib file’s owner, it must have been created before the nib file is loaded.
In order to achieve this for your window controller, you need to set the file’s owner class to ListingWindowController. You must not drag an object cube and instantiate the window controller inside the nib file. The window controller is the owner of the nib file, so it must exist before the nib file is loaded. You must also link the window outlet in file’s owner to the top-level window object in the nib file so that the window controller is aware of what window it should manage.
Having done that, use:
wc = [[ListingWindowController alloc] initWithWindowNibName:#"ListingWindow"];
instead of:
wc = [[ListingWindowController alloc] initWithWindowNibName:#"ListingWindow" owner:self];
since wc is supposed to be the owner of the nib file.
View controllers work similarly. They’re created before loading the nib file, are responsible for loading a nib file that contains a view as a top-level object, are that nib file’s owner, and have a view outlet that must be linked to that top-level view.
It’s not clear from your question whether you have a separate nib file for the view. If you don’t, then using a subclass of NSViewController is not needed at all — you could use a subclass of NSObject instead. If you insist on using NSViewController to manage a view that’s not loaded from a separate nib file, then you should override -loadView so that you get a reference to the view by some means other than loading it from a nib file, and sending it -setView: so that it is aware of the view it’s supposed to be managing.
Recommended reading: Nib Files in the Resource Programming Guide, NSWindowController class reference, NSViewController class reference.

Related

Where is the window outlet in an NSDocument

My app was converted from a non-document-based app to a document-based one. I did that by creating a subclass of NSDocument, called Document. I also created a Document.xib and set its "File's Owner" to Document.
Now in Document.xib, I can see there is a window outlet in its "File's Owner". I don't have a window outlet defined in Document. Where does it come from? I guess it is from the super class NSDocument, but I have no access to that variable in Document. What's up with this weird window outlet?
Have a look at the documentation for -[NSDocument setWindow:]
This method is invoked automatically during the loading of any nib for which this document is the file’s owner, if the file’s owner window outlet is connected in the nib. You should not invoke this method directly, and typically you would not override it either.
NSDocument doesn't deal with NSWindows directly, but it keeps a list of NSWindowControllers that you can access via the -[NSDocument windowControllers] method. My guess is that when setWindow: gets called, it wraps the window in a new NSWindowController and adds it to the list.
You should be able to access the window with something like this:
NSWindowController* controller = self.windowControllers.lastObject;
NSWindow* window = controller.window;
I just made a new project to test it, and this works:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
[super windowControllerDidLoadNib:aController];
NSLog(#"%#", [self.windowControllers.lastObject window]);
}

nsobject controller linked across two nib (xib) files. declaring nib instantiation

Apple's resource programming guide (RPG) states "it is better to distribute components across multiple nib files."...
therefore,
i have an associate window nib (Nib 2) that has an nsobjectcontroller that needs to be linked (selection self) to a nsarraycontroller in the main document window nib (Nib 1).
i need to share a common instance (either the nsarraycontroller in nib 1 or nsobjectcontroller in nib2). I can add a custom object in Nib 1. and set the File's Owner to that type of custom object. however, each nib instantiates their own instance.
is there a method of setting which nib an object was instantiated, or declaring an external reference.
i also "Make the File’s Owner the single point-of-contact for anything outside of the nib file" (RPG). Which is a NSWindowController.
Thanks in advance.
You probably want to make the owner of NIB1 responsible for instantiating NIB2. This will allow it to be the owner of both NIBs. In the common case, it might look something like this:
// In the interface...
#property (nonatomic, readwrite, retain) NSArray* nib2TopLevelObjects;
// In the implementation...
- (void)awakeFromNib
{
NSNib* nib2 = [[[NSNib alloc] initWithNibNamed: #"NIB2" bundle: [NSBundle mainBundle]] autorelease];
NSArray* tlo = nil;
[nib2 instantiateWithOwner: self topLevelObjects: &tlo];
self.nib2TopLevelObjects = [tlo retain];
// Do other stuff...
}
- (void)dealloc
{
[_nib2TopLevelObjects release];
[super dealloc];
}
At the end of this, NIB2 will have been instantiated with NIB1's owner as it's owner as well, and NIB2 will have plugged its objects into the shared owner (be sure not to plug things into the same outlet from both NIBs.)
All that said, I'm not sure this is necessarily the right pattern to use here. If these windows are both views upon the same document, you should probably make an NSWindowController subclass for each window and override -[NSDocument makeWindowControllers] to instantiate them. (The NSWindowController will be the "File's Owner" for each NIB.) Having the document NIB's owner be the NSDocument subclass is a "short cut" for simple situations. Once you need multiple windows, NSWindowControllers are the way to go.
Each NSWindowController can get back to the document via -document and the NSDocument subclass can coordinate state between the different NSWindowControllers. This is a cleaner approach, and avoids all the shenanigans with clobbered IBOutlets, etc.
For your specific case, I could see having a property like sharedArrayController on the NSDocument subclass that gets the NSArrayController from NIB1 during -makeWindowControllers, and re-vends it. Then you can then access it from NIB2 by binding to File's Owner > document.sharedArrayController.selection.

Receive window notifications

I have an NSWindow set up in Interface Builder. I have set the class of File's Owner to my NSWindowController and linked the window property of the controller to my NSWindow.
My controller implements NSWindowDelegate.
Now, in my controller, I have added the following:
- (void)windowDidLoad
{
[super windowDidLoad];
[self.window setDelegate:self];
}
- (void)windowDidBecomeMain:(NSNotification *)notification
{
NSLog(#"Did become main.");
}
Still, -windowDidBecomeMain: isn't called. Does anyone know why this is?
EDIT:
Trying to show a window from AppDelegate on launch. The main nib (declared in Info.plist) contains a menu item only which is linked to the AppDelegate. In the application delegate, I show an icon on the status bar and when this icon is clicked, I display the menu from the main nib.
In the application delegate, I also want to display a window which should have a window controller assigned to take care of the logic.
I believe that when this works, I will receive my window notifications.
Now, the following code doesn't show the window and I can't figure out why.
DemoWindowController *dwc = [[DemoWindowController alloc] initWithWindowNibName:#"DemoWindowController"];
[dwc showWindow:self];
Note that self is the application delegate.
I suspect your problem is due to the fact that your window controller is not actually the object that is the nibs file owner.
When you change the class in interface builder you are telling it what outlets and actions are available (which is why you are able to drag to the window outlet) but you are still responsible for passing in this object yourself.
In the case of a non-document based application, you will have a main method which calls NSApplicationMain. What this does is basically look up and load the window nib that is specified in your info.plist file and pass the current NSApplication instance to this nib as the files owner (so even though you changed the class type to NSWindowController, the object being passed in is actually of type NSApplication).
The easiest way to fix your problem is to get rid of your window controller for now (as it isn't actually doing anything yet).
You should implement the -windowDidBecomeMain: method in your app delegate. Then Ctrl+drag from your window to your appDelegate to set it as the delegate of the window to get your notifications.
Update
To answer your question regarding the WindowController beware of the following two issues:
You are creating your window controller variable (dwc) in your applicationDidFinishLaunching: method. This is released the moment you leave the method taking your window with it. Create an instance variable to hold onto the window controller instead.
Ensure that your second window nib has its file owner set to NSWindowController (or your window controller type) and that its window outlet is connected to the window in the nib file.
Your window should now display.

Reference to subclassed NSWindowController returns its document - is this correct?

I am new to document-based applications and hence I may have missed something fundamental. I have written a document based application which uses a subclassed NSWindowController for the interface and a subclassed NSDocument for the model. Per the documentation I initialise the windowController in makeWindowControllers and load its xib. In interface builder, the xib has my windowController subclass set as File's Owner. Among the views in the window, I have a subclass of NSOutlineView and the NSOutlineView datasource and delegate are also refenced in the nib and connected to the windowController via IBOutlets.
According to the documentation, I should be able to access the document from the OutlineView datasource via [windowController document]. However, referencing the windowController (via IBOutlet) from the OutlineView datasource gives me the document instead!
This has lead to some rather ugly code in the OutlineView datasoure (which is a subclass of NSObject in the windowController's xib) to get hold of the document, eg:
-(MyDocument *)myDocument {
MyDocument *theDocument = (MyDocument *)myWindowController;
return theDocument;
}
Where the IBOutlet in the header file references myWindowController as:
IBOutlet MyWindowController *myWindowController
In brief - why does an IBOutlet connected to the windowController get me the document directly instead in this situation? The above code works but seems as if it shouldn't.
Edit: clarification
Okay, I worked out the answer to this one - don't accidentally set the File's Owner of the xib to the NSDocument instead of the windowController in another part of your code and forget that you did it! This overrides the File's Owner that you previously set in the xib.

NSViewController and multiple subviews from a Nib

I'm having a difficult time wrapping my head around loading views with Interface Builder and NSViewController.
My goal is to have a view which meets the following description: Top bar at the top (like a toolbar but not exactly) which spans the entire width of the view, and a second "content view" below. This composite view is owned by my NSViewController subclass.
It made sense to use Interface Builder for this. I have created a view nib, and added to it two subviews, laid them out properly (with the top bar and the content view). I've set File's Owner to be MyViewController, and connected outlets and such.
The views I wish to load in (the bar and the content) are also in their own nibs (this might be what's tripping me up) and those nibs have their Custom Class set to the respective NSView subclass where applicable. I'm not sure what to set as their File's Owner (I'm guessing MyController as it should be their owner).
Alas, when I init an instance of MyViewController none of my nibs actually display. I've added it to my Window's contentView properly (I've checked otherwise), and actually, things sort of load. That is, awakeFromNib gets sent to the bar view, but it does not display in the window. I think I've definitely got some wires crossed somewhere. Perhaps someone could lend a hand to relieve some of my frustration?
EDIT some code to show what I'm doing
The controller is loaded when my application finishes launching, from the app delegate:
MyController *controller = [[MyController alloc] initWithNibName:#"MyController" bundle:nil];
[window setContentView:[controller view]];
And then in my initWithNibName I don't do anything but call to super for now.
When breaking out each view into its own nib and using NSViewController, the typical way of handling things is to create an NSViewController subclass for each of your nibs. The File's Owner for each respective nib file would then be set to that NSViewController subclass, and you would hook up the view outlet to your custom view in the nib. Then, in the view controller that controls the main window content view, you instantiate an instance of each NSViewController subclass, then add that controller's view to your window.
A quick bit of code - in this code, I'm calling the main content view controller MainViewController, the controller for the "toolbar" is TopViewController, and the rest of the content is ContentViewController
//MainViewController.h
#interface MainViewController : NSViewController
{
//These would just be custom views included in the main nib file that serve
//as placeholders for where to insert the views coming from other nibs
IBOutlet NSView* topView;
IBOutlet NSView* contentView;
TopViewController* topViewController;
ContentViewController* contentViewController;
}
#end
//MainViewController.m
#implementation MainViewController
//loadView is declared in NSViewController, but awakeFromNib would work also
//this is preferred to doing things in initWithNibName:bundle: because
//views are loaded lazily, so you don't need to go loading the other nibs
//until your own nib has actually been loaded.
- (void)loadView
{
[super loadView];
topViewController = [[TopViewController alloc] initWithNibName:#"TopView" bundle:nil];
[[topViewController view] setFrame:[topView frame]];
[[self view] replaceSubview:topView with:[topViewController view]];
contentViewController = [[ContentViewController alloc] initWithNibName:#"ContentView" bundle:nil];
[[contentViewController view] setFrame:[contentView frame]];
[[self view] replaceSubview:contentView with:[contentViewController view]];
}
#end
Should not MainViewController be a subclass of NSWindowController? And the outlets in the class connected to view elements in the main Window in MainMenu.xib?
Let's hope old threads are still read...

Resources