Putting a UINavigationController within a UITabbarController

There seem to be quite a few people asking how to use a UINavigationController within a UITabbarController so I decided to put together this very quick tutorial.  We will build a very small (useless) app which will consist of the following…

  • A tab bar with two tab bar items
  • The first tab item will have a standard view controller
  • The second tab item will have a view controller backed by a navigation controller
  • A button which pushes a new view onto the nav controller
  • All of these views will be bound together programatically in the app delegate.

So our application won’t do much – but you’ll get to see one way of making it all fit together.  Okay, here goes.

Step 1 : Create the project

First of all, lets create a new project in XCode. Go to File -> New Project and choose a “Window-based project” (make sure the core data box is NOT selected). I’m going to (creatively) call the project Test.

Now that you have your project, single click the Classes group and go to File -> New File and from the Cocoa Touch Classes, choose “UIViewController subclass”. Make sure the ‘With XIB for user interface’ is selected and click Next. Give it the name “PlainViewController” and make sure you’re creating the .h file too, then click Finish.

Repeat that process again and create another UIViewController. This time, call it “NavRootView“.  And just so we can demonstrate the movement to a new view using the navigation controller, create a third and final UIViewController. Call this one “NavSecondView“.

Open up NavRootView.h.  We are going to add a UIButton action which will take us to our second view.  Add the code so the file looks like this…

@interface NavRootView : UIViewController {

}

- (IBAction)buttonPressed;

@end

That’s right, only the one line was added. This allows interface builder to see this action and we will wire this to a button later. Save and close the file.

Step 2 : Do some very small Interface Builder changes

Now we have our basic files, we need to put a button into our root view so that we can show the movement to the second view. Open up NavRootView.xib in interface builder by double-clicking on it. From the library, drag over a UIButton (Rounded rect button) and plonk it anywhere in the view. Change it’s title to something like “Next page”. Now, right-click drag from the button to File’s Owner. When the context menu pops up, choose ‘buttonPressed’. We now have a connection for our button – we will put the actual action code in shortly. Save and close the xib file.

Next, open up “NavSecondView.xib” in interface builder. All we are going to do here is change the colour of the background so we know we are looking at another view. Click inside the view somewhere and in the inspector (attributes tab) change the background color to a horrid bright colour. I find an off-yellow particularly offensive. Then save and close the xib file.

Finally, open up “PlainViewController.xib” in interface builder and just drag and drop a label on there. Put anything you want in there, just so we know this is our plain (non navigation controller) view. Save and close.

Step 3 : Put it all together

We now have the views all ready, lets put all the pieces together. We are going to arrange the views when the application is loaded. Lets start by opening up TestAppDelegate.m and changing the didFinishLaunchingWithOptions: method, so it now looks like this…

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

	// Create the PlainViewController (and give it a title)
	PlainViewController *plainView = [[PlainViewController alloc] initWithNibName:@"PlainViewController" bundle:nil];
	[plainView setTitle:@"PlainView"];

	// Create the NavRootView controller (and give it a title)
	NavRootView *navRoot = [[NavRootView alloc] initWithNibName:@"NavRootView" bundle:nil];
	[navRoot setTitle:@"NavRoot"];

	// Create our navigation controller using our NavRootView as it's root view
	UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:navRoot];

	// Make an array containing our plain view controller and our navigation controller
	NSArray *viewArray = [NSArray arrayWithObjects:plainView, navController, nil];

	// Release the views and nav controller
	[plainView release];
	[navRoot release];
	[navController release];

	// Create our tab bar controller
	UITabBarController *tabbarController = [[UITabBarController alloc] init];

	// Tell the tab bar controller to use our array of views
	[tabbarController setViewControllers:viewArray];

	// Finally, add the tabbar controller as a subview of the app window
	[window addSubview:[tabbarController view]];

	[self.window makeKeyAndVisible];
	return YES;
}

I’ve commented what’s happening in the code, but essentially we are doing this…

  • Creating our two starting view controllers (one for each tab)
  • Creating a navigation controller which uses our NavRootView controller as it’s root view
  • Creating an array of the plain view controller and the nav controller (with it’s root view)
  • Telling the tabbar controller to use the views in this array for each tab item

Step 4 : Make the button work

Finally, we need to implement the method which will fire when the button is pressed. Open up NavRootView.m and add the following code after the @implementation line…

- (IBAction)buttonPressed {
	NavSecondView *secondView = [[NavSecondView alloc] initWithNibName:@"NavSecondView" bundle:nil];
	[self.navigationController pushViewController:secondView animated:YES];
	[secondView release];
}

When our button is pressed, the second view will be created and pushed onto the navigationController stack (with animation). Our back button is automatically created with the title of the root view when the new view is pushed.

And that’s all there is to it.  My favourite way of creating tab bar controllers with various types of view / controller.  Any questions – just ask and I’ll try to help.