As far as I can tell, sometimes it is indeed necessary to subclass UINavigationBar, to do some nonstandard restyling. It s sometimes possible to avoid having to do so by using categories, but not always.
Currently, as far as I know, the only way to set a custom UINavigationBar within a UIViewController is via IB (that is, via an archive) - it probably shouldn t be that way, but for now, we gotta live with it.
This is often fine, but sometimes using IB isn t really feasible.
So, I saw three options:
- Subclass UINavigationBar and hook it all up in IB, then muck about loading up the nib each time I wanted a UINavigationController,
- Use method replacement within a category to change the behaviour of UINavigationBar, rather than subclassing, or
- Subclass UINavigationBar and do a little mucking about with archiving/unarchiving the UINavigationController.
Option 1 was unfeasible (or at least too annoying) for me in this case, as I needed to create the UINavigationController programmatically, 2 is a little dangerous and more of a last-resort option in my opinion, so I chose option 3.
My approach was to create a template archive of a UINavigationController, and unarchive that, returning it in initWithRootViewController
.
Here s how:
In IB, I created a UINavigationController with the appropriate class set for the UINavigationBar.
Then, I took the existing controller, and saved an archived copy of it using +[NSKeyedArchiver archiveRootObject:toFile:]
. I just did this within the app delegate, in the simulator.
I then used the xxd utility with the -i flag, to generate c code from the saved file, to embed the archived version in my subclass (xxd -i path/to/file
).
Within initWithRootViewController
I unarchive that template, and set self to the result of the unarchive:
// This is the data from [NSKeyedArchiver archivedDataWithRootObject:controller], where
// controller is a CTNavigationController with navigation bar class set to CTNavigationBar,
// from IB. This c code was created using xxd -i
static unsigned char archived_controller[] = {
0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd4, 0x01, 0x02, 0x03,
...
};
static unsigned int archived_controller_len = 682;
...
- (id)initWithRootViewController:(UIViewController *)rootViewController {
// Replace with unarchived view controller, necessary for the custom navigation bar
[self release];
self = (CTNavigationController*)[NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithBytes:archived_controller length:archived_controller_len]];
[self setViewControllers:[NSArray arrayWithObject:rootViewController]];
return [self retain];
}
Then, I can just grab a new instance of my UIViewController subclass which has the custom navigation bar set:
UIViewController *modalViewController = [[[CTNavigationController alloc] initWithRootViewController:myTableViewController] autorelease];
[self.navigationController presentModalViewController:modalViewController animated:YES];
This gives me a modal UITableViewController with a navigation bar and toolbar all set up, and with the custom navigation bar class in place. I didn t need to do any slightly-nasty method replacement, and I don t have to muck about with nibs when I really just want to work programmatically.
I would like to see the equivalent of +layerClass
within UINavigationController - +navigationBarClass
- but for now, this works.