Thursday, 21 July 2011

NSViewControllers (Part 1)

I seem to see a lot of people asking questions about NSViewControllers on Stack Overflow and I wondered about them as well for a quite some time. They are being used more frequently in newer API on Cocoa, and you can't program for iOS without them.

There are a few functions the NSViewController allows you to do:

  1. Create new views on the fly
  2. Provide a title for the view for displaying in a tab or popup menu
  3. Enable you to bind to a value in the view, before the view is actually created.
Lets look at each one of these separately.

Creating a view on the fly is used quite a lot, think of a complex application like iTunes. The left hand column has a list of different options and when you select one, it shows a different view in the right hand side of the window. Now I have no idea how iTunes does it, but this is the one of the UI designs that NSViewController is designed to help with.

NSViewController has a method - (NSView *)view which creates a view and returns it. The simplest use of NSViewController is like so

NSViewController *viewController = [[NSViewController alloc] initWithNibName:@"customView" bundle:nil];
NSView *view = [viewController view];

[parentView addSubview:view];

This simply creates a new NSViewController and tells it that the definition for the NSView is found in the customView.nib (or customView.xib) file in the main bundle. Then it creates a new NSView and puts that view into parentView. But really, using NSViewController in this way doesn't really add much over just creating the view directly. Where NSViewController is really useful is when you need to create the views on the fly. Lets assume, for the sake of argument, that iTunes does use NSViewController. When you're looking at the Music view you may not want the Movies view loaded into memory. NSViewController does this by not creating the view until you call -(NSView *)view or -(void)loadView. This allows you to create the view controller and hook things up to it, without having the view loaded and taking up memory until it's needed. Lets look at a more full example.

In our application delegate (called ViewControllerExample1AppDelegate in the example project), we have some iVars that get filled from the MainWindow.nib.

@interface ViewControllerExample1AppDelegate : NSObject <NSApplicationDelegate> {
    NSWindow *window;
    NSBox *box;
    NSViewController *testView1Controller;
    NSViewController *testView2Controller;

@property (assign) IBOutlet NSWindow *window;
@property (assign) IBOutlet NSBox *box;
@property (assign) IBOutlet NSViewController *testView1Controller;
@property (assign) IBOutlet NSViewController *testView2Controller;

- (IBAction)switchView1:(id)sender;
- (IBAction)switchView2:(id)sender;

In the MainWindow.nib we've created a window with 2 NSButtons, and an empty NSBox. The two buttons are hooked up to the switchView1 and switchView2 methods and we'll look at what they do later on. The NSBox is connected to the box iVar. In the nibfile we've created two NSViewControllers and connected them to the appropriate iVars. The ultimate aim for this is that clicking on the first button will switch the view contained in the NSBox to the view from the first view controller, and the second button with switch it to the second view controller's view. A simple but common UI pattern.

We also need to create two nib files that the NSViewControllers use to generate their views. I just did this by creating a new file, selecting UI in the type column, and selecting View in the type. That gives a nib file that only has an NSView in it, and I just dragged whatever controls I wanted in the view. After these nib files have been created, we need to tell the NSViewControllers what nib file to do, and to do that you just need to set the Nib Name property on the NSViewController.

Whenever NSViewController is sent the loadView or the view message, it creates the view from the nib file and it sets the File's Owner object to the receiver, which is our NSViewController and this it uses the File's Owner object's view outlet to find out what NSView should be returned. This means that we have to connect it up in our nib files. The first part of this process is to set the type of the File's Owner object. By default it is of type NSObject, which isn't very useful for us, so in Interface Builder we change the class type property by selecting the File's Owner object, and in the inspector pane's Identity Inspector changing where it says the class is NSObject to NSViewController. Now if we try to ctrl-drag from the File's Owner placeholder object to the NSView, it will allow us to set that as the view outlet.

Finally, all we need to do is to make the buttons switch the views and that simply means creating the view and setting it in the box. We do this in the switchView1 and switchView2 actions, which look like this:

- (IBAction)switchView1:(id)sender
    NSView *view = [testView1Controller view];
    [box setContentView:view];

- (IBAction)switchView2:(id)sender
    NSView *view = [testView2Controller view];
    [box setContentView:view];

The controllers are simply asked for their views, and the NSBox is told to show it. The returned view is set to be autoreleased, so if you want to keep it around then you need to send it the retain message. In this instance the setContentView message sent to box does the retaining for us so when the content view is replaced the old view is released.

We can make the program slightly more complete by displaying a box when it initially starts by doing the same thing in the awakeFromNib message sent to the application delegate.

- (void)awakeFromNib
    NSView *view = [testView2Controller view];
    [box setContentView:view];

I'll look into the other things you can do with NSViewController in the next part as this seems to be getting quite long.

The project for this example can be downloaded from (sorry its on Mediafire, hopefully it won't be taken down)
Apple's NSViewController documentation for Cocoa:

Links to the other parts of the NSViewController articles
Part 2:-
Part 3:-
Part 4:-

No comments:

Post a Comment