Where is the window outlet in an NSDocument - xcode

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]);
}

Related

Under what conditions can an NSWindowController's document change?

My app observes the document property of its NSWindowController and performs some UI setup when it is set. Once it has been set, it would be difficult to rebuild the UI (for internal reasons) as the result of a change.
Once an NSWindowController has set its document property to an opened document, under what conditions will the system ever change that property to a new NSDocument instance (i.e., will the document ever be swapped out)? I've never observed it happening, but I can imagine features like versions or iCloud syncing causing the window controller's document to get swapped for a new document. However, the documentation on the NSWindowController lifecycle doesn't seem to touch on the issue.
It doesn't change once it has been set. NSWindowController gets its document by addWindowController method. Every new/opened document creates it's own windowController. Instance of document doesn't change with iCloud or revertChanges. It is up to you how to synchronise document with its views (redrawing).
/* Create the user interface for this document, but don't show it yet.
The default implementation of this method invokes [self windowNibName],
creates a new window controller using the resulting nib name (if it is not nil),
specifying this document as the nib file's owner, and then invokes [self addWindowController:theNewWindowController] to attach it. You can override
this method to use a custom subclass of NSWindowController or to create more
than one window controller right away. NSDocumentController invokes this method
when creating or opening new documents.
*/
// e.g. override
- (void)makeWindowControllers
{
if ([[self windowControllers] count] == 0) {
MainWindowController *controller = [[MainWindowController alloc] init];
[self addWindowController:controller];
}
}

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.

NSWindowController object linkage in Interface builder

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.

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.

In NSWindowController subclass, [self document] returns null

I am using a custom subclass of NSDocument and a custom subclass of NSWindowController. The problem is that I cannot reference my custom document from my custom window controller.
In IB, in the TKDocument NIB I have File's Owner set to TKWindowController.
In my TKDocument subclass I have:
- (void) makeWindowControllers {
TKWindowController *controller = [[TKWindowController alloc] init];
[self addWindowController:controller];
}
Then in my TKWindowController subclass I overrode setDocument to make sure it was being called:
- (void) setDocument(NSDocument *) document {
NSLog(#"setDocument:%#", document);
[super setDocument:document];
}
and then (again in TKWindowController) my action which references the document itself:
- (IBAction) plotClicked:(id) sender {
TKDocument *doc = [self document];
NSLog(#"plotClicked %#", doc);
}
The NSLog in setDocument outputs the string returned by my [TKDocument description] override as I'd expect; I only put it there to see if it was being called. However, doc in plotClicked is null.
What might I have done wrong?
EDIT: I believe the problem is to do with NIBs. My Document has its own NIB with File's Owner set to the custom controller as mentioned above. The plotClicked action is fired from a menu item in MainMenu.xib. I believe it's hitting a new instance of the controller which isn't associated with the current, active document.
So, how do I link the two? My question is really this: How do I obtain a handle to the current active document (or its windowcontroller) from MainMenu.xib?
Thanks
My Document has its own NIB with File's Owner set to the custom controller as mentioned above.
The File's Owner of a document nib should be the document. Consider that suspect #1.
The plotClicked action is fired from a menu item in MainMenu.xib. I believe it's hitting a new instance of the controller which isn't associated with the current, active document.
Did you put a window controller inside your main menu nib? If not, then that isn't the problem, since you must have wired up your plotClicked: menu item to the First Responder, and the window controller and its document will be in the responder chain.
If you did, then there's the solution: delete the window controller from the MainMenu nib and hook up your menu item to the First Responder, so that the action message goes down the responder chain, which will enable it to hit the document or window controller.
How do I obtain a handle to …?
The only Handles on the Mac come from Carbon; those Handles do not exist in Cocoa.
init is not a designated initializer of NSWindowController. You want one of these: – initWithWindow:, – initWithWindowNibName:, – initWithWindowNibName:owner:, or – initWithWindowNibPath:owner:.
Also, from the docs:
In your class’s initialization method,
be sure to invoke on super either one
of the initWithWindowNibName:...
initializers or the initWithWindow:
initializer. Which one depends on
whether the window object originates
in a nib file or is programmatically
created.

Resources