iPhone Tab Bar and Navigation

We've had a lot of rather philosophical blog posts lately (mostly related to the BOSS text searching system). It seems time to put that aside a moment and get back to some nice, solid iOS coding.

A common pattern for iPhone apps is a tab bar on the bottom, with a navigation stack on each tab. Today, we'll look at how to set up such a structure in code.  (And we'll do it in pure, unsweetened Cocoa for that nostalgic old-school feel.)

To do this, start with the simplest Xcode iPhone template, "Window-Based Application." Then find your application delegate's application:didFinishLaunchingWithOptions: method, which is where all the setup work is going to go.

From the top down, here's the big picture of what we need to build:

1. The window view should have one subview, which is the tab bar controller's view.

2. The tab bar controller has a list of view controllers, one for each tab. Each of those controllers is, more specifically, a navigation controller.

3. Each navigation controller has a root view controller. This is just a plain old view controller, whose view contains whatever is at the topmost (root) level of the navigation stack for that tab.

It's always a good idea to divide the code up into small methods that each do one thing. Let's look at these bottom-up, starting with the method that contains code for setting up just one view.

- (UIView *)makeDemoContent1 {
	UIView *view = [[UIView alloc] initWithFrame:self.window.bounds];
	
	UILabel *label = [[CompLabel alloc] initWithFrame:CGRectMake(10,100, 320,30)];
	label.text = @"Hello world!";
	[view addSubview:label];
	[label release];
	
	return [view autorelease];
}

This method creates an autoreleased UIView, and stuffs content therein. We've just made a little Hello World label, but of course you could put whatever you want here, or even init the view from a NIB file if you're so inclined.

Next, let's make a method to create the tab that holds this content.

- (UIViewController *)makeDemoTab1 {
    UIViewController *rootCtrl = [[UIViewController alloc] init];
    rootCtrl.title = @"Demo Tab 1";
    rootCtrl.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Demo 1"
         image:[UIImage imageNamed:@"tab1icon.png"] tag:1];
    [rootCtrl.view addSubview:[self makeDemoContent1]];

UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:rootCtrl]; navController.navigationBar.barStyle = UIBarStyleBlack; navController.navigationBar.tintColor = [UIColor purpleColor];

[rootCtrl release]; return [navController autorelease]; }

This creates a standard view controller for the tab content, which is also where we set the view title (that appears in the navigation bar) and the tab bar title and icon. We stuff our demo content into this, and then wrap it in a UINavigationController. This is also a sensible place to tweak the look of the navigation bar, as we've done above.

So, now we basically have one tab, including its content. Make a similar pair of methods for each of the other tabs you want. Then construct the tab bar itself:

- (UITabBarController *)makeTabBar {
    UITabBarController *tabCtrl = [[UITabBarController alloc] init];
    tabCtrl.viewControllers = [NSArray arrayWithObjects:
                               [self makeDemoTab1],
                               [self makeDemoTab2],
                               [self makeDemoTab3],
                               nil];
    tabCtrl.delegate = self;
    return [tabCtrl autorelease];
}

Easy peasy, isn't it? All that's left is stuffing the tab bar view (which is automatically created behind the scenes by the UITabBarController) into the window. Back in your application:didFinishLaunchingWithOptions: method, just add this line:

    [self.window addSubview:[self makeTabBar].view];   

That's all there is to it. But as one more refinement, you might want to implement some of the UITabBarControllerDelegate methods, most likely didSelectViewController. A common behavior seen in many apps (including Apple's) is to pop the navigation stack back up to the top whenever switching tabs. You can achieve that with this easy delegate method:

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
    // When we switch tabs, pop back to the root view.
	[(UINavigationController*) viewController popToRootViewControllerAnimated:NO];
}

And that really is it. I hope you've found this useful, and if you have any suggestions to add (or errors to correct!) please post in the comments below.