41
votes

I have used NSWindowController in projects several times, and feel like I have a (very)rough grasp of the concepts behind this important class. What I would like to do with this post is to clarify/correct my own understandings, and hopefully help other learners get that first step into understanding. It's the at-a-glance concepts, overview, and best practices that I find is most useful, and often lacking in the documentation. Here is my take on NSWindowController (questions are interspersed in bold):

  • An NSWindowController (NSWC) subclass exists (conceptually) just beneath every window nib, acting as the glue between the user interface elements and the model objects that they control/represent. Basically, every window in your application should have its own NSWC subclass.
  • The File's Owner of the nib should always be the NSWC subclass. Is this the case even for the MainMenu.xib application?
  • The NSWC window property should always be linked to the NSWindow in InterfaceBuilder.
  • You should override the 'init' method, using [super initWithWindowNibName:], so that when you refer to [mycontroller window] it will load the nib. Should this also be the case for the NSWC for the MainMenu.xib window, even though this is opened at startup?
  • The NSWC shouldn't do too much heavy lifting - it should simply pass messages to instances of objects, and present those objects in the UI.
  • It can modify the UI using binding, or acting as a delegate for tables etc., or by actively changing the UI elements when it observes a change, or a combo of any of the above (which one you use seems to be a matter of taste, with pros and cons on all sides).
  • An NSWC can create instances of other NSWCs when necessary (for example, when opening a one-off sub-window).
  • Use the [mycontroller showWindow:nil] to display the associated window at the front. If you want the window to appear as a sheet, use something like:

    NSWindowController* mycontroller = [[MyController alloc] init];
    [NSApp beginSheet: [mycontroller window]
       modalForWindow: [self window] 
        modalDelegate: self 
       didEndSelector: @selector(didEndMySheet:returnCode:contextInfo:)
          contextInfo: nil];
    

The didEndSelector: should be a method of the NSWC of the parent window, and can access and release 'mycontroller' with [sheet windowController]. - To close the window call the performClose: method of NSWC's window.

Some Questions:

  • Should the NSWC of the MainMenu window also be the application delegate, or should this be a different class?
  • In the same vein, should the main NSWC handle files (drag/drop and opening), or should it be passed on to the app delegate, or is that just a matter of taste?

Please correct me if any of this is bad practice, or is just plain wrong. I am looking to clarify my understanding of NSWindowController, so any additions (in the form of best practices, experiences, gotchas) would be highly appreciated.

Thanks, Laurie

2

2 Answers

30
votes

What are window controllers actually for?

Window controllers are tools to load a window from a NIB file and for managing the memory of the resources allocated in the NIB. Before there where NSWindowControllers one basically had to write the same code for every window or invent an own window controller class.

Of course they are also controllers in the Model/View/Controller sense, so they are the right place to connect the views from the window to the model objects. To do this they often need to act as the delegate or data source for a view object. So you got this part perfectly right.

Also window controllers are a tool for code reuse. It makes it easy to drop the window controller class and it’s XIB/NIB into another project and use it there.

So yes, every window from a NIB should be owned by a window controller, with one exception. Actually, this is just a guideline for good code, nothing enforces it.

WindowControllers and MainMenu.xib

MainMenu.xib is a different thing, there you can’t use a window controller. This NIB gets loaded by NSApplication so this has to be it’s "Files owner". There is no way to get a window controller between the NSApplication and the NIB. It also isn’t necessary to use a window controller for memory management there, since the application object lives for the entire runtime of the program, so it doesn’t have to clean up it’s resources from the NIB when it gets deallocated.

If you really need a window controller for your main window you cannot put this in the MainMenu.xib.

I hope this helps. There probably is a lot more to say about window controllers too

7
votes

Is this the case even for the MainMenu.xib application?

No, the MainMenu nib is owned by NSApplication (that's who loads it).

Should this also be the case for the NSWC for the MainMenu.xib window, even though this is opened at startup?

No, NSApplication loads the main nib based on your applications file's "NSMainNibFile" property. (It just happens to be pre-set to "MainMenu" in the template Xcode projects.) If you want to change its name then change it there (and rename your nib file). (BTW: This property can also be changed in your target's "Summary" view in Xcode 4.)

Should the NSWC of the MainMenu window also be the application delegate, or should this be a different class?

The owner of the NSMainNibFile nib is the instance of NSApplication that loads it and by association any delegate of that instance. Neither of these are NSWC sub-classes.

In the same vein, should the main NSWC handle files (drag/drop and opening), or should it be passed on to the app delegate, or is that just a matter of taste?

There is no "main NSWC" (The app/app-delegate is the controller for the NSMainNibFile).

All drag-n-drop operations are handled by NSWindow or NSView sub-classes. I usually use a special NSWindow or NSView sub-class that just passes all drag-n-drop methods thru to the delegate. For example:

- (unsigned int) draggingEntered:sender
{
    return [[self delegate] draggingEntered:sender];
}

This way I can keep all my window/view code together in their respective controller (as determined by their nib owner). And because the window/view specific code is in the controller (not the NSWindow/NSView subclass) different types of NSWindows/NSViews can all use the same drag-n-drop sub-classes.