iOS Dev Notes

iOS Development From The Frontlines

UINavigationController Tutorial

with 20 comments

I wanted to write a UINavigationController tutorial because it’s one of the most common parts of iPhone apps (and iPad apps, although less so). They have a few gotchas that can make learning a little confusing.

In this tutorial I’ll start with the basic elements of using a UINavigationController, then get into some of the more complex things you can do with them.

Creation

UINavigationController is a subclass of UIViewController, but unlike UIViewController it’s not usually meant for you to subclass. This is because navigation controller itself is rarely customized beyond the visuals of the nav bar.

An instance of UINavigationController can be created either in code or in an XIB file with relative ease. It’s thought of as a stack: it has a root view controller, and then new view controllers can be pushed onto the stack (often when the user taps on a row in a table) or popped off the stack (often by pressing the back button).

The root view controller can be set in an XIB by dragging the view controller under the navigation controller, or in code by using initWithRootViewController when you create it.

The Navigation Stack

Four methods are used to navigate user through the stack:

  • – pushViewController:animated:
  • – popViewControllerAnimated:
  • – popToRootViewControllerAnimated:
  • – popToViewController:animated:

Generally you want animated set to YES, especially if it’s in response to a user action. I’ve seen some apps that don’t animate transitions between view controllers, and I always find it distracting.

You don’t have to keep track of the UINavigationController instance among all the view controllers on the stack. UIViewController has a property called navigationController which will return the associated navigation controller for the view controller. If it doesn’t have one, navigationController returns nil.

If you’re going to push a new view controller on the stack in response to the user tapping a row in a table, that might look like this:

- (void)tableView:(UITableView *)tableView
        didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [[tableView cellForRowAtIndexPath:indexPath] setSelected:NO animated:YES];
 
    DetailsViewController *detailsViewController = [[DetailsViewController alloc]
        initWithNibName:@"DetailsViewController" bundle:nil];
    [self.navigationController pushViewController:detailsViewController];
    [detailsViewController release];
}

Two things to note here. One is that I deselected the row in the table before pushing a new view on the stack. This is another mistake I sometimes see apps make, and it’s odd when you tap on a row, then tap back, and the old row is still selected.

Also note that I released the details view controller after pushing it on the navigation stack. Pushing a view controller adds it to the navigation controller’s viewControllers array, which means it gets retained. Keep that in mind to make sure you don’t leak memory.

You don’t have to worry about coding up the left “back” button in the navigation bar. It will pop the stack by default.

Configuring the Navigation Bar

Probably the most confusing part of navigation controllers when you’re first using them is how to configure the navigation bar: what text to use for the left back button, the navigation title and the right button (if you want one).

You might think you’d need to access the navigation bar directly from the navigation controller, but it usually doesn’t work that way. Rather, every view controller has a “navigation item”, which is an instance of UINavigationItem.

UINavigationItem deserves some special attention. Don’t confuse it with UINavigationBar: it’s not a UIView. Rather, it’s a class that represents the information that will be used to set what’s displayed in the navigation bar when the view controller is on the stack. This might seem weird at first, but you’ll get used to it.

Let’s extend the DetailsViewController example above. Say it’s actually showing details about a particular person. When a DetailsViewController gets pushed onto the navigation stack, you want the title of the navigation bar to be the person’s name. The code might look something like this:

- (void)tableView:(UITableView *)tableView
        didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  [[tableView cellForRowAtIndexPath:indexPath] setSelected:NO animated:YES];
 
  Person *person;
 
  // Some code that sets person based on the particular cell that was selected
 
  DetailsViewController *detailsViewController = [[DetailsViewController alloc]
    initWithNibName:@"DetailsViewController" bundle:nil];
  detailsViewController.navigationItem.title = person.name;
  [self.navigationController pushViewController:detailsViewController];
  [detailsViewController release];
}

Notice that we’re not accessing the navigation bar itself. Also note that we actually set the title before pushing the view controller on the stack. When the view controller gets pushed, UINavigationController will take care of updating its navigation bar by looking at the new view controller’s navigation item.

If a view controller will always have the same title, you can set it in the “title” property of UIViewController. UINavigationController will use the view controller’s title by default if one hasn’t been set it its navigation item. This can be set in XIBs.

The Back Button

By default, when you push a new controller on the stack, the title of the back button will be set to the title of the previous view controller’s navigation item.

Apple seems to like this behavior, as you see it in most of their own apps. However, sometimes you may want to customize the back button’s title. In particular, clients often want me to always have it say “Back” (perhaps because they’re used to web browsers). I usually discourage this, but sometimes it makes sense, especially if titles are particularly long.

The tricky part with this is that although UINavigationItem has a backBarButtonItem property, it’s set to nil by default. Even if you try to set the title on backBarButtonItem, it won’t work since it’s nil. I’m honestly not sure why this is the case, but that’s how it is. The other thing to remember is that backBarButtonItem describes what the back button should be when the item is the next to last item in the stack. In other words, you’re describing what you want the back button to be like when this item is back one level.

So if you want to make sure the back button always has the title of “Back” when a new view controller pushes this view controller back, you can write:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
      style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backButton;
[backButton release];

Left and Right buttons

The navigation item has a property called “leftBarButtonItem” which is different than the backBarButtonItem. This really only makes sense for the root view controller, since for every other view controller you’ll want a back button in the left of the bar.

There’s also a “rightBarButtonItem” for the space to the right of the navigation bar’s title.

In both cases, you just set them to a UIBarButtonItem instance that you create. Say you want a button called “Settings” as the right button. You might use code like the following:

UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithTitle:@"Settings"
      style:UIBarButtonItemStylePlain target:self action:@selector(handleSettings)];
self.navigationItem.rightBarButtonItem = settingsButton;
[settingsButton release];

This will call a method named “handleSettings” when the button gets pressed.

Hiding Navigation Bar at the Root Level

Since the view controller at the root often doesn’t have a left or right button, it’s not too uncommon to want the navigation bar hidden when the root view controller is visible, but to appear when another view controller gets pushed on the stack. You should probably only do this when a title really isn’t necessary.

It can be a little tricky to get this to look right without the navigation bar appearing or disappearing oddly as you push or pop the stack. The place to do it is in the viewWillAppear: and viewWillDisappear: methods of the view controller:

- (void)viewWillAppear:(BOOL)animated {
	[super viewWillAppear:animated];
 
	[self.navigationController setNavigationBarHidden:YES animated:YES];
}
 
- (void)viewWillDisappear:(BOOL)animated {
	[super viewWillDisappear:animated];
 
	[self.navigationController setNavigationBarHidden:NO animated:YES];
}

This should cause the navigation bar to change seamlessly as the views are changing.

Conclusion

I hope you’ve found something useful in this UINavigationController tutorial regardless of your level of expertise. I haven’t gone over every aspect of navigation controllers as it would have made this post boring and dry. If you want boring and dry reading, there’s always the Apple docs :).


Looking to add push notifications to your iOS app? Check out PushLayer, a service from the authors of iOSDevNotes.

Bookmark and Share

Subscribe for updates

Fill out the form below to join my newsletter. I'll also send you a bonus copy of my ebook, "5 Steps To Becoming a Better iOS Developer".

Written by cwalcott

March 15th, 2011 at 12:25 am

20 Responses to 'UINavigationController Tutorial'

Subscribe to comments with RSS or TrackBack to 'UINavigationController Tutorial'.

  1. thx for making this tutorial . it’s not boring and useful!

    Boshan

    30 Sep 11 at 1:05 pm

  2. Nice job! Great tutorial with very helpful information.

    UtmostLogic

    12 Oct 11 at 7:09 am

  3. That was usefull, thanks.

    neef

    25 Oct 11 at 8:01 am

  4. Nice article. Concise. Thanks!

    Virginia

    1 Nov 11 at 10:29 am

  5. Thank you very much.

    I have a doubt about creating navigation bar. If I have an app with a tab bar and a navigation bar in each view (related to each tab):

    Do I have to add a navigation bar in the MainWindow.xib or Do I have to add a navigation bar in the main view of each tab?

    BR.

    Ladinamo

    7 Nov 11 at 5:48 am

  6. Each tab should have its own UINavigationController. It’s easiest to set this up programmatically (such as in the app delegate).

    cwalcott

    8 Nov 11 at 4:19 pm

  7. I have a question here, what happens when you want to reload the data? Would that appear on viewWillAppear, but then don’t you need to create an IBOutlet for tableView itself? What I am facing is that I have done the tableView portion but I am trying to invoke the deselction of the row method in viewWillAppear than didselectRowatIndex, however since I have a complex structure, somewhere it is not getting implemented right. However, its a great tutorial, thanks for clarifying few doubts.

    Karim

    23 Nov 11 at 5:33 pm

  8. Hi Karim, if your view controller is a UITableViewController, you shouldn’t have to create an IBOutlet for the tableView. Otherwise you may have to. You can always call deselectRowAtIndexPath on the table view to deselect the row.

    cwalcott

    8 Dec 11 at 11:33 am

  9. A pleasure to read something so clearly written. (I find a lot of Apple’s explanations turgid.)

    wjs

    11 Jan 12 at 10:21 pm

  10. Great tutorial! It helped me get familiar with UINavigationController very quickly!

    Mark H

    23 Jan 12 at 2:27 pm

  11. Wonderful article. Detailed to the level it should be and concise enough to keep the interest level intact. Really helped me to understand the concepts. Thanks

    Kanishk

    2 Feb 12 at 8:09 am

  12. Thanks for such a concise and informative tutorial

    kamu

    2 Feb 12 at 9:41 pm

  13. Can you post a source code please?

    Preda

    27 Feb 12 at 3:52 pm

  14. Great tutorial!

    nethip

    13 Mar 12 at 1:44 am

  15. Your tutorial is what taught me UINavigationController.

    One thing that I like to point out, when you set the back button in the viewDidLoad of the view (any view), it is actually only displayed when a new view is pushed on the stack and not for the view that sets it.

    Is there a way to change the root view controller to be anything other than a UITableViewController ? I am thinking of keeping the navbar but adding other content in the view.

    Further, any thought on adding a Tab Bar at the bottom of the root view controller (once it’s not a UITableViewController) ?

    Thanks alot

    Bendigi

    13 Mar 12 at 10:59 pm

  16. I think I solved my first issue, simply changed the rootviewcontroller to a simple UIViewController, then added a view in IB and connected it to the view outlet of the file owner and now I have my cool view WITH the navbar. Only thing left is the Tab Bar.

    :)

    Bendigi

    14 Mar 12 at 12:29 am

  17. [...] here and here. A nice little tutorial on how to set up a UINavigationController can be found here, and a previous SO question here covers the topic of custom animations, while some source code [...]

  18. No clue how to use the navigation bar after reading your tutorial , could you post a tutorial with IB please ?

    Thanks!

    pat

    23 May 12 at 10:50 am

  19. Thanks, man. This is a great, really well-written intro. I’ve found the relationship between UINavigationController and UIViewController subviews a bit perplexing, but this really cleared it up.

    Erik

    1 Aug 12 at 2:23 am

  20. Nice post. Thank you.

    One small update: If you’re using ARC you don’t need the calls to release.

    Mike Breen

    25 Sep 12 at 10:02 am

Leave a Reply