PrEV
Thoughts from a NeXTStep Guy on Cocoa Development

Demystifying View Controllers and Views

Feb 03, 2009 by Bill Dudney

Continuing down the path of trying to help demystify stuff on the iPhone...

While teaching the last iPhone Studio I noticed that several people hit bugs in the apps they built because they either forgot to hook up the view outlet in IB or had mistakenly disconnected this connection. While we stress the importance of this link in class we don't have time to go into the details of why it is so important. I hate that, I'd so love to have the time to dig into the details (of course it probably would not help because its just too much for a beginner but still :). So to try to make me feel better about not having the time in class I figured I'd post something here on my blog to help people understand what is going on.

If you want to follow along I created a 'view based application' in Xcode. I changed nothing in the generated code except adding log messages and methods to the resultant view controller.

The reason the 'view' connection is so important starts here. Specifically when the applicationDidFinishLaunching: method is called, the call to viewController.view invokes the 'get' method for the view property on the view controller.

That get method is what sets off the chain of events that require the view be set. To understand what is going on requires a little bit of digging. Let's get started with the main nib file;

Our view controller is created in the main nib file, the object lives here (i.e. its as if the object were 'alloc'd and inited in this file'). However it's view does not live here. If you open the connections inspector with the view controller selected (command-2) you will see that this view controller has one inbound connection but no outbound connections.

Here is the screen shot.

This is as it should be, we don't want the whole application defined in one method and we don't want the whole user interface defined in one nib file.

But how is the magic of the view connection accomplished without the view being in this nib file?

To understand that we need to start in the attributes inspector of the view controller. Select the VC in the main window nib file and hit command-1, you should see something that looks like this;

Notice here that we have set the nib file name. When our code asks the VC for its view (back in the applicationDidFinishLaunching:) the VC will first check to see if it's view is nil. Since we did not connect the view in this nib file the view will indeed be nil. If a VC's view is nil and it has a nib file name specified it will load the nib file via the loadNibNamed:owner:options: method on the NSBundle class. The VC will pass self in as the 2nd argument (i.e. the owner) and the name specified in the nib file as the first argument (the nib file name).

Once the nib file is loaded the VC will assert that the view is set. This is where the error messages and confusion comes into play. The message looks like this;

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "ListViewController" nib but the view outlet was not set.

The message is often confusing because people have not had a chance to dig into these details. They read the message but don't understand what its trying to say.

When you create a project from scratch the view is set for you but if you accidentally break the connection or you create a new view based interface file you will have to set it yourself.

Now let's look at how the view comes to be set by loading the nib file.

Open your view controller's nib file (the one we saw in the attributes inspector above) and select the File's Owner object and then open the identity inspector (command-4). You should see something like this;

The class of the File's Owner is set to the class of our view controller. Since the File's Owner object does not live in the nib file (remember it is just a proxy) we need to let IB know what the class is so IB knows which connections can be made. Remember that this was set up for us by the template we choose when creating this project so you don't need to change it now.

Having the class set gives IB enough information to know what attributes are available for connection. With the File's Owner object still selected bring up the connection inspector (command-2). You should see something like this;

Notice that the view is connected to the object named 'View' in the nib file.

Remember that the view controller is created in the MainWindow.xib file and is then passed into the nib loading machinery as the File's Owner. As the nib file is loaded any connections to the File's Owner object are connected to the 'real object' that was passed in as the owner argument. And by this our view gets set.

I hope this short article helps you to get your head wrapped around how view's get set for view controllers. Sorry for making it not seem like magic, and happy building!



Comments:

Hi Bill,

By calling the project, ViewControllerView you've ended up with Xcode setting up a class called ViewControllerViewViewController. I would suggest that this class should be renamed, even if to something like "ExampleViewController", to help demystify these things. In doing so we impress upon those learning Cocoa that the names of classes should reflect something of their domain. In a real-world application this would be, for example, "ListViewController".

I would also suggest that the default naming of nib files in the Apple-provides templates is misleading. A nib itself is designed to be the place where a view is designed, thus it should not have a name appended with "Controller". If the owner of the nib were to be "ExampleViewController" then the nib should be named as "ExampleView.nib" (or .xib). Thus describing the differing roles of the view and its controller. The current practice will be leading to most of the confusion.

Hope you're well, looking forward NSConference.

Jonathan

Posted by Jonathan Dann on February 04, 2009 at 06:59 AM MST #

Hi Jonathan,

NSConference is going to be great!

Agreed on Apple's naming convention. Have you filed a bug yet? I have not but probably should.

I was hoping that I could be lazy with the code but you are right, I'm updating the entry as soon as I commit this comment.

Posted by Bill Dudney on February 04, 2009 at 06:59 AM MST #

Yeah I've filed it as rdar://6554890

Thanks for updating the tutorial!

Posted by Jonathan Dann on February 04, 2009 at 07:42 AM MST #

Exactly the solution I was looking for, and with a great explanation to boot! Thanks very much.

Posted by Kai Schaller on February 23, 2009 at 09:22 PM MST #

Hi Bill
I found this post while trying to puzzle out the following : in sample project
http://www.stanford.edu/class/cs193p/downloads/11-Pickers.zip
you would find that the PickersViewController instantiates modally the TypeSomethingViewController thus:
- (IBAction)showTypeSomething:(id)sender
{
TypeSomethingViewController *typeSomethingViewController = [[TypeSomethingViewController alloc] init];
typeSomethingViewController.delegate = self;

[self presentModalViewController:typeSomethingViewController animated:YES];

[typeSomethingViewController release];
}
There is a coresponding TypeSomethingView.xib in the project, and indeed the view presented correspond to the view in xib.

The mystery is : how does the system 'know' that it should load this nib file in
[[TypeSomethingViewController alloc] init]

since there is no visible instruction to that effect in the project code.

I verified that adding to TypeSomethingViewController the following

// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// if (self = [super initWithNibName:@"TypeSomethingView" bundle:nibBundleOrNil]) {
}
NSLog(@"%s", __FUNCTION__);
return self;
}

also works, in either version.

It is as if the designated initializer of UIViewController has some logic saying
"if (nibNameOrNil == nil), look nevertheless for a nib file that names this class as file's owner and load it if found".

Any clarification would be appreciated.

Posted by Rudi Farkas on May 13, 2009 at 03:21 PM MDT #

Hi Rudi,

Yes you are correct, who knows if that is a feature or bug. IMO the nib file names should always be without the 'Controller' at the end, but who am I to argue with Apple :).

Marcus did a great write up on this at his 'Cocoa is my Girlfriend' blog.

Posted by Bill Dudney on May 13, 2009 at 03:23 PM MDT #

This is exactly what I needed to read. I was pulling my hair out trying to figure out how to reasonably separate my view screens -- every other example I've seen jams everything into a single huge .xib -- and this is the only article I've found that shows how this setup works.

Thank you, Bill!

Posted by Jonathon Stierman on October 28, 2009 at 05:25 AM MDT #

HI Bill

Thanks a lot, this is the first clear tutorial of this magic and I was tearing my hair out trying to figure this out. I had a question:

When I create a Navigation based application, the NIB file for the Navigation Controller object does not seem to be set by XCode. Whereas, creating a regular View Controller application does seem to set this.

Is this because a navigation controller does not by itself have a view and instead depends on the RootViewController to have a view object?

Raven

Posted by Raven on December 14, 2009 at 06:50 AM MST #

Hi Raven,

You hit the nail on the head, a nav controller depends on the current view controller for what to display. In the nib file created from the template its 'Root View Controller'.

Good luck!

Posted by bill Dudney on December 14, 2009 at 06:53 AM MST #

This was a great help, thanks for your effort.

Posted by Jarryd Hall on August 15, 2010 at 09:41 PM MDT #

Hi Bill,

Great post, definitely cleared up some of the more murky aspects of IB for me.

Thanks.

James

Posted by James on August 15, 2010 at 09:41 PM MDT #

I have just started learning xcode/iphone and somehow broke that link. Your article got me back on my track. Thanks a bunch!

Posted by Sanjay on September 06, 2010 at 04:00 PM MDT #

Post a Comment:
  • HTML Syntax: Allowed