Objective-c – (Cocoa) What’s the equivalent of a UIViewController subclass in a single window cocoa app


After messing around with iOS apps, I thought I'd give a Cocoa app a whirl assuming things would be very similar. So I have a single window app with a split view and some NSTableViews (think iTunes) and I'm looking for a place to put my code that will fetch data from the web to fill the tables.

In iOS I would put this in the viewDidLoad method of the appropriate UIViewController subclass. The UITableViewDataSource would then access this data to populate its cells.

Now I have an NSWindow for which I could do a similar thing but how do I make a NSWindowController for it? Is this even what I want? I could put it in the AppDelegate but doesn't seem right.

Best Solution

The difference between iOS and OS X is that there's only one window in iOS but there can be multiple in OS X. NSViewControllers in OS X work differently than UIViewControllersin that the NSViewController is designed to load and handle a single view, while UIViewControllers (apologies for my relative lack of iOS knowledge) seem to handle multiple views and provide much more of the glue between views.

Even the Apple docs write that:

In Mac OS X, AppKit view controllers are assistants to the window controller, which is ultimately responsible for everything that goes in the window.

Hence, the counterpart for UIViewController in OS X isn't NSViewController, but rather NSWindowController, which does provide for OS X much of what UIViewController does for iOS by managing the entirety of an individual window and the layout/content/interaction of the views within.

In your case, I would use an NSWindowController - though if the app is very simple, the App Delegate works too; and if the app is very complex, then using a NSViewController to split up the code wouldn't be a bad idea.

The best way to do use an NSWindowController would be to programatically load it in the App Delegate using [[CustomWindowController alloc] init] and

@implementation CustomWindowController
- (id)init
    self=[super initWithWindowNibName:@"CustomWindowNibName"];
        //perform any initializations
    return self;

Or calling

[[CustomWindowController alloc] initWithWindowNibName:@"CustomWindowNibName"];

directly (and overriding initWithWindowNibName) if you want it to be reusable.

And you can delete the default window in MainMenu.xib.

Fundamentally, more often than not, an NSWindowController manages a window instantiated in its own nib file. The NSWindowController usually owns that nib file. (Though it is possible to have it manage a programmatically created window, that isn't usually how it's done.)

To be able to use a custom NSWindowController, you therefore need to make your window-to-be-managed in a separate nib/xib file. (using the default xib file means that you allow Cocoa to automatically instantiate a NSWindowController without opportunity for subclassing; you cannot use a custom NSWindowController with the default NSMainNibFile. For a simple app, just put all the controller code in the NSApplication/App Delegate)

In Xcode 4 at least, the process involves creating a xib with the window template and the custom NSWindowController class, instantiating the CustomWindowController class based on that nib in -init or wherever and then calling [CustomWindowController showWindow:self]; (or if that doesn't work, -makeKeyAndOrderFront:) to get the window to actually show (in - (void)applicationDid/WillFinishLaunching:(NSNotification *)aNotification might be a nice place).

To stop the default window from showing I just delete it. There's probably a better way but I don't know that.