For an iPhone UI I’m developing, I need to have one UINavigationController nested inside another, and to have the inner UINavigationController’s events push a view on to the outer one’s stack. CocoaTouch didn’t give this to me for free, but there was a simple solution.
This post assumes that you’re familiar with the fundamentals of iPhone programming, including view controllers, UINavigationController, and delegates. There is sample code for this post, which is released under version 2 of the WTFPL.
The Problem
I’m working on a multi-step UI for a game. Expressing these steps as a regular drill-down table-style UI on the iPhone felt cumbersome, and games can’t afford for processes to feel cumbersome; people will stop playing. One solution that occurred to me was batching related sets of steps in a smaller navigation table – so in Step 1 you’d drill through substeps 1A, 1B, and 1C before moving to Step 2. The fact that the whole view wouldn’t be replaced with every choice seemed like it would be less destructive of the user’s mental context, and the chunking of substeps should make it easier for the users to wrap their heads around the process. (No word yet on whether this solves my UI problem, but I like it so far.)
My UI solution contained its own technical problem, though: If I’m expressing the process steps in a UINavigationController, and expressing the substeps in a nested UINavigationController, how does the inner navigation controller notify the outer one that the user’s last substep choice completes that step and it’s time to move to the next step – or in programmatic terms, how does the inner UINavigationController tell the outer one to push a new view onto the outer one’s stack?
Or, putting it more visually, how do I get from here:

…to here:

In the non-nested situation when you wanted to push a new view onto a UINavigationController’s stack, you’d do the following in the current view:
[self.navigationController pushViewController:nextViewController animated:YES];
So in my nested case, I need to do that, but pushing onto the outer navigation controller’s stack from a view controller on the inner navigation controller’s stack. I tried a number of naive (but sensible-seeming) targets for the pushViewController:animated: action, such as:
self.navigationController.navigationController
self.navigationController.parentViewController.navigationController
Nothing worked. I set a breakpoint and drilled down through the members of self.navigationController, and no path to that outer UINavigationController was apparent.
The Solution
While investigating the innards of UINavigationController, I stumbled upon a writable property that looked like it might help: The delegate. When creating the inner UINavigationController, I added one line of code:
innerNavCntlr.delegate = self; // THIS IS THE MAGIC, PART 1
Keep in mind that this is called in the view controller on top of the outer navigation controller’s stack, so “self” has access to the outer navigation controller.
When the view on top of my inner navigation controller’s stack is ready to signal the outer navigation controller, I do the following:
// THIS IS THE MAGIC PART 2
UIViewController *topVC = (UIViewController *)self.navigationController.delegate;
[topVC.navigationController pushViewController:nextCntlr animated:YES];
A couple of notes: First, the view controller that creates the inner UINavigationController must implement UINavigationControllerDelegate, or you’ll get a compiler warning – but you can just declare that it does so in the header file, as none of the methods in that interface are required.
Secondly: If you’re like me, this feels like an abuse of the delegate property. Now there’s no reason that you couldn’t actually use that object productively as the inner navigation controller’s delegate – I just haven’t done so here. And the fact that the delegate property has to be casted to be used this way says to me that it wasn’t meant for this – explicit casts are always a code smell. (Anal-retentive types might want to put in some type-checking around that cast for safety.)
That said, it works, and I haven’t run into a maintainability problem yet, as this code doesn’t really get re-used in many places. Were I expecting to use this trick often, I might package up a subclass of UINavigationController (call it NestedNavigationController) that actually took an outer UIViewController or UINavigationController property. Then again I’m finding that in Cocoa, subclassing is often a code smell…
Got a better solution? I’m interested – please leave a comment below!
Update: Whoops. Comments are enabled now.