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.
What about something like this from your inner nav controller:
MyAppDelegate *delegate = (MyAppDelegate*)[[UIApplication sharedApplication] delegate];
[delegate.outerNavController pushViewController:nextStepViewController animated:YES];
Where MyAppDelegate has a member variable outerNavController that is a UINavigationController or your own customer subclass of course.
I guess it’s a matter of style; I like to have pointers to the primary views of my application from my app delegate class so I can make these kinds of transitions from anywhere to anywhere. I don’t do this for secondary views that should only be accessible from the primary views.
Jonathan – thanks for the idea! And yeah, I can see where that would be convenient. The only thing I don’t love about it is the tight coupling this introduces – now all your subviews know about each other and about the implementation of the app delegate. I’m more comfortable passing the enclosing view controller to the responsible subview and telling it “notify this thing when you’re done”.
But, as you say, it’s a matter of style.
Hi Brad, how did you manage to nest the functionality of the UINavigationController inside of another view? I’m having some trouble with this; some sample code would be greatly appreciated
You’ve done it once again. Superb writing.
This is definitely a brilliant work upon this Navigation issue. I really appreciate your wonderful sharing. … It’s truly great!
Brad,
A wonderful solution to an esoteric, yet nagging problem. I cannot even begin to tell you how much your write-up helped me. The delegate functionality is absolutely brilliant and in almost any case where I have ever wanted to do something crafty, I almost always find a way to do so by leveraging the power of delegation. Thanks so much for sharing this wonderful solution for making it simple to have nested UINavigationControllers that can retain full application interoperability!!!
Glad I could be of help.