Real iPhone Crap 2: initWithNibName:bundle: is the designated initializer of UIViewController
Before I go into another shortcoming of UIKit, I’d like to make it plain that I genuinely like most of UIKit. In many ways it is much better than AppKit. I love UIControl, and I think that UITableViewCell being a subclass of UIView is a huge step forward. But, the weaknesses are the parts that demand difficult design decisions, so I’m delving upon them in this series.
Like UIViewController, NSDocument was designed to be subclassed. They both can also load a nib file. Let’s look at how NSDocument does it.
The designated initializer of NSDocument is init. How then, does the subclasser specify the nib file to load? He implements windowNibName:
- (NSString *)windowNibName
{
return @"CrapDocumentNib";
}
What is cool about this?
- The name of the nib file is in exactly one place — inside the controller class that will be using the nib file
- The NSDocument subclass and its nib file become a module that can be easily reused in other applications
- Classes that use the NSDocument subclass don’t need to know anything about the nib file or whether it uses a nib file at all
In short, this design means that we can reap all the benefits of object encapsulation.
The designers of UIViewController, on the other hand, decided to make the designated initializer:
- (id)initWithNibName:(NSString *)n bundle:(NSBundle *)b;
(I suspect that this had something to do with making it possible to specify the nib file inside Interface Builder:

“Gosh, Wally, that’s going to make for a great demo!”)
If you make initWithNibName:bundle: the initializer for your UIViewController subclass, wherever you create an instance, you must specify which nib file goes with it:
CrapViewController *cvc;
cvc = [[CrapViewController alloc] initWithNibName:@"CrapViewNib"
bundle:nil];
This makes no sense: the name of the nib file should be in one place, and that place should be inside CrapViewController.m. I see most developers putting the name of the nib file in a dazzling array of places: in code wherever the view controller is created and in nib files where view controllers are created.
Stylish iPhone programmers, however, are changing the designated initializer of their UIViewController subclass to init:
- (id)init
{
[super initWithNibName:@"CrapViewNib" bundle:nil];
… give ivars initial values…
return self;
}
Now being a fastidious Objective-C programmer, you must also override the designed initializer of the superclass to call your designated initializer:
- (id)initWithNibName:(NSString *)n bundle:(NSBundle *)b
{
return [self init];
}
Voila! The benefits of object encapsulation are restored to your application:
CrapViewController *cvc = [[CrapViewController alloc] init];
(Can I mention that I, personally, don’t instantiate view controllers in nib files. Many of my view controllers act as the owner of nib files, but I always create the view controller itself programmatically. I don’t have a great argument for this, but I do find it easier to understand the work of developers who follow this guideline.)
Before I close this, I’d like to you to imagine how much easier it would have been if the UIViewController had followed the design of NSDocument. When you opened up any UIViewController subclass you would immediately look for the viewNibName method:
- (NSString *)viewNibName
{
return @"CrapViewNib";
}
Now you know which nib file goes with the view controller. If the method returns nil, you would know that the view controller doesn’t use a nib file. If the method didn’t exist at all, you would know that the nib file’s name was the same as the class name.
Instead of two init methods, there would be one init method if you needed to initialize some instance variables and none if you didn’t.
And life would be a little bit better.
If you missed the last installment, you might want to read Real iPhone Crap 1.
Posted by Aaron Hillegass on August 13th, 2009 under iPhone.
Comments: 13
Comments
Comment from Steve
Time: August 13, 2009, 3:42 pm
Make a class method like this for each view controller you write:
+ (MyViewController*)newController;
{
MyViewController *result = [[MyViewController
alloc]initWithNibName:@”MyViewController” bundle:nil];
return [result autorelease];
}
Comment from Administrator
Time: August 13, 2009, 4:02 pm
@Steve: Apple says that methods that start with “new” should not autorelease their results.
Pingback from Big Nerd Ranch Weblog » Xcode Templates
Time: August 13, 2009, 7:46 pm
[...] Hillegass’ last post on initializers for UIViewController was spot-on. In fact, when you come to our iPhone Bootcamp, [...]
Comment from Charles Parnot
Time: August 14, 2009, 10:54 am
Nice post. Interestingly, this is exactly one of the things that has bothered me on the Mac side with NSViewController, and I use the exact same technique. Also, I don’t like the view controller to be in the nibs. One of the practical reason for me and how I rationalize it is as follows. Since the view controller is the owner of the nib, it will be init-ed by either (1) in code, (2) by another nib, (3) being the owner of the main nib as set in the info.plist of the app (talking about Mac app, here). The option (3) is obviously not appropriate. And then the option (2) would work and get the view controller nib also loaded (since we have a nice `init` method that knows which nib to use). However, you then get `awakeFromNib` called twice, and in my experience, cascading nib loading is not a good idea. So you are left with (1), create the view controller in code. The nice thing is that it’s one easy line of code, same for the iPhone or the Mac:
CrapViewController *cvc = [[CrapViewController alloc] init];
Comment from Lee Falin
Time: August 15, 2009, 9:09 am
Aaron,
Great series, but I do have one question about your parenthetical statement:
“Can I mention that I, personally, don’t instantiate view controllers in nib files.”
How then, do you connect the outlets of the controller to the UI? Do you do it programatically or am I missing something in your statement.
Comment from Charles Parnot
Time: August 15, 2009, 7:45 pm
Lee: “How then, do you connect the outlets of the controller to the UI? Do you do it programatically or am I missing something in your statement.”
The UIViewController is still the **owner** of a nib file, which means it can not technically init-ed by the nib. The connections can still be set in the nib, though.
Comment from Joe Conway
Time: August 16, 2009, 1:07 pm
To make that more clear:
When we have a XIB file, we have two types of objects: archived objects and proxy objects. Archived objects live inside the XIB file. When a XIB file is loaded, NSBundle looks at all of the archived objects and sends the alloc message to their class and then initializes them. When the XIB file is finished loading, we have a set of brand new objects just like we would if we had alloc/init’ed them. The view, its subviews, and any other top-level objects are examples of archived objects.
The File’s Owner of a XIB file is a proxy object. When the XIB file is loaded, NSBundle does not create an instance of the proxy object’s type. That object already exists, you allocated it and then initialized it with this XIB file in code. We call these objects proxy objects, because within the XIB file, they represent the real object in your code.
When Aaron says “instantiate view controllers in nib files”, he means that an instance of a UIViewController is created when the XIB file is loaded. To do this, you would have to add a UIViewController instance to your doc window (a top-level object). He is saying that all of his UIViewControllers are being instantiated in his code – there is always a visible alloc/init for every one of them.
There are three benefits to NOT instantiating view controllers in a XIB file (and yes, I will tell you never ever ever to instantiate view controllers in XIB files):
1. The object hierarchy is defined only in code. You don’t have to look at a XIB file to see all of the controller objects that may exist in your project.
2. XIB files for a view controller are loaded when a VC is sent the message view. (Usually, this is because you do something like [window addSubview:[vc view]]; or because a nav/tab controller uses that view.) If a VC “A” is an archived object in a XIB file for VC “B”, A will not get loaded until B’s view goes on the screen. Typically, you want your controllers alive at all times.
3. Here is the most important one. Imagine you have two view controllers, A and B, and B is instantiated within A’s XIB file. If a low memory warning occurs when A is not on the screen, it trashes its view. When A goes back on to the screen, it reloads its view from its XIB file. Of course, it will also create a new instance of B and release the old instance. Any state stored in B is gone, and its view needs to be unnecessarily loaded again. Even scarier: if within B’s logic, it pops a nav controller back to A after a low memory warning and then sends messages to itself, you will crash.
Comment from Ben S. Stahlhood II
Time: October 26, 2009, 2:50 pm
The methods that Aaron discussed are good, but as of 3.0 you can also pass nil to both of the params of initWithNibName:bundle: or just do the alloc/init and it will look for a XIB with the same name as the UIViewController class. Of course, this can cause confusion if trying to still code for pre 3.0 and may not be as readable as actually specifying what is being loaded.
Comment from Andy Warwick
Time: March 22, 2010, 10:32 am
In the code for the designated initializer of their UIViewController, shouldn’t ‘bundle:nib];’ read ‘bundle:nil];’?
Comment from Administrator
Time: March 22, 2010, 11:16 am
Andy, you are right. It is fixed now.
Comment from Fat Johnnie
Time: April 5, 2010, 4:11 am
Aaron, thanks for this subtly insightful post!
Here’s a question that might interest you. This is one of those very confusing problems that is SO COMMON you use it in almost every app, but (as far as I know) there is no clean obvious clear solution. I really hope I am missing something obvious.
Have an app with a custom UIViewController, I mean a subclass of UIViewController called SuperiorUIViewController. Have an instance of it called xxx.
Of course, xxx will have a UIView associated with it.
Next create a custom UIView, I mean a subclass of UIView, called SuperiorUIView.
(Very typically, you might want to use drawRect in there, for example.)
Next, make a SuperiorUIView be the view “in” (for want of a better term) your controller xxx. In other words the view which xxx drives will not be a normal UIView, but rather a custom subclass of UIView.
{Aside – I find setting the view of a custom UIViewController, to be a custom UIView, to be quite confusing and it is not really explained well, anywhere I can find. There are a number of ways to do it, all of them confusing. It is particularly confusing and annoying if you want to “change” midproject the view of a UIViewController from the normal given UIView to a new subclass of UIView, such as SuperiorUIView. I reckon one day you should write the definitive tutorial on this, for both the “I use XIB” and “I don’t use XIB” cases! For example, I for one simply did not know about the “NIB Name” in IB until reading this; I thought the only way to make a controller have a UIView subclass as it’s view was to use the ‘initWithNibName: bundle:’ paradigm.}
Anyway, so everything’s great and the view of xxx is now a custom SuperiorUIView rather than just a UIView.
So here’s the problem. Whenever you do this:
[self.view someFunctionOrAnother];
it works perfectly but you GET A WARNING.
Because, of course, xxx.view is still a UIView, it was (I assume) declared as a UIView somewhere in the guts of cocoa that is far beyond me.
What the heck is the REAL solution to this?
Of course you can do something like
SuperiorUIView *stupidSolution = (SuperiorUIView *)self.view
but that’s just silly. (I think?)
It is profoundly unsatisfying that the initWithNibName system (or the impress-your-friends NIB Name system in IB) will load a custom UIView subclass and “put it in” to xxx, but, view still remains as a UIView rather than a UISuperiorView.
Please help us Aaron you’re our only hope, what’s the real deal ?! Thanks !!!
I really hope I’m just missing something stupidly obvious! Thank you!
Comment from James DiPalma
Time: May 8, 2010, 2:49 am
@Fat Johnnie
Looks like you are directly accessing your superclass’s instance variables and getting a warning because that ivar is declared as a UIView. Avoiding this warning will require you to recast “view” as a (SuperiorUIView*) somewhere (or use an id cast variable). Maybe casting withing method call feels cleaner for you:
[(SuperiorUIView*)self.view someFunction];
Comment from James DiPalma
Time: May 8, 2010, 12:08 pm
Using Interface Builder to define user interface flow is a choice some programers may prefer. Having a button target/action a UIViewController to open a window feels intuitive.
As Joe Conway mentions, some prefer having their object hierarchy defined in code. Using Interface Builder implies putting some objects within a XIB file; adding controller and flow objects to a xib file that contains views may work for some programmers.
In some cases like a button that shows a view (like in iPad’s Mail app when in portrait mode). If a button is pushable, it is onscreen, so your uicontroller is instantiated when its activating button is instantiated; having this controller “alive at all times” has some value, but if it can only be asked to load its view when a button gets pressed, having its life cycle match that button’s life cycle seems like a fair design.
Joe’s crashing-on-low-memory scenario may not apply if this uicontroller was only able to show its view when its activating button was on screen.
In some cases I see little technical downside to xib-instantiated UIViewControllers.
Write a comment