Combined View Controller Interfaces

You can use the view controllers that the UIKit framework provides by themselves or in conjunction with other view controllers to create even more sophisticated interfaces. When combining view controllers, however, the order of containment is important; only certain arrangements are valid. The order of containment, from child to parent, is as follows:

Modal view controllers represent an interruption, they follow slightly different rules. You can present almost any view controller modally at any time. It is far less confusing to present a tab bar controller or navigation controller modally from a custom view controller.

The following sections show you how to combine table view, navigation, and tab bar controllers in your iOS apps. For more information on using split view controllers in iPad apps, see Split View Controllers. For more information on table views, see Table View Programming Guide for iOS.

Adding a Navigation Controller to a Tab Bar Interface

An app that uses a tab bar controller can also use navigation controllers in one or more tabs. When combining these two types of view controller in the same user interface, the tab bar controller always acts as the wrapper for the navigation controllers.

The most common way to use a tab bar controller is to embed its view in your app’s main window. The following sections show you how to configure your app’s main window to include a tab bar controller and one or more navigation controllers. There are examples for doing this both programmatically and using Interface Builder.

Tab bar views do not support translucency and tab bar controllers never display content underneath their associated tab bar. Therefore, if your navigation interface is embedded in a tab of a tab bar controller, your content may underlap the navigation bar if you adopt a full-screen layout as described in Adopting a Full-Screen Layout for Navigation Views, but it does not underlap the tab bar.

When embedding navigation controllers in a tab bar interface, you should embed only instances of the UINavigationController class, and not system view controllers that are subclasses of the UINavigationController class. Although the system provides custom navigation controllers for selecting contacts, picking images, and implementing other behaviors, these view controllers are generally designed to be presented modally. For information about how to use a specific view controller, see the reference documentation for that class.

Creating the Objects Using a Storyboard

To create a combined interface with three tabs that contain custom view controllers and one tab that contains a navigation controller:

  1. Create three custom view controllers, one for each tab, and a navigation controller.

  2. Select the three custom view controllers and the navigation controller (only the navigation controller scene, not it’s root view controller).

  3. Choose Editor > Embed In > Tab Bar Controller.

  4. Display the tab bar controller as the first view controller by selecting the option Is Initial View Controller in the Attributes inspector (or present the view controller in your user interface in another way.)

Creating the Objects Programmatically

If you are creating a combined tab bar and navigation interface programmatically for your app’s main window, the most appropriate place to do so is in the applicationDidFinishLaunching: method of your application delegate. The following steps explain how to create a combined interface where three tabs contain custom view controllers and one contains a navigation controller:

  1. Create the UITabBarController object.

  2. Create three custom root view controller objects, one for each tab.

  3. Create an additional custom view controller to act as the root view controller for your navigation interface.

  4. Create the UINavigationController object and initialize it with its root view controller.

  5. Add the navigation controller and three custom view controllers to your tab bar controller’s viewControllers property.

Listing 6-1 shows the template code needed to create and install three custom view controllers and a navigation controller as tabs in a tab bar interface. The class names of the custom view controllers are placeholders for classes you provide yourself. The init method of each custom view controller is also a method that you provide to initialize the view controllers. The tabBarController and window variables are declared properties of the application delegate class, which maintains strong references to those objects.

Listing 6-1  Creating a tab bar controller programmatically

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    self.tabBarController = [[UITabBarController alloc] init];
 
    MyViewController1* vc1 = [[MyViewController1 alloc] init];
    MyViewController2* vc2 = [[MyViewController2 alloc] init];
    MyViewController3* vc3 = [[MyViewController3 alloc] init];
    MyNavRootViewController* vc4 = [[MyNavRootViewController alloc] init];
    UINavigationController* navController = [[UINavigationController alloc]
                            initWithRootViewController:vc4];
 
    NSArray* controllers = [NSArray arrayWithObjects:vc1, vc2, vc3, navController, nil];
    tabBarController.viewControllers = controllers;
 
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    window.rootViewController = tabBarController;
    [window makeKeyAndVisible];
}

Even if you create your custom view controllers programmatically, there are no restrictions on how you create the view for each one. The view management cycle is the same for a view controller regardless of how it is created, so you can create your views either programmatically or using Interface Builder as described in Creating Custom Content View Controllers in View Controller Programming Guide for iOS.

Displaying a Navigation Controller Modally

It is perfectly reasonable (and relatively common) to present navigation controllers modally from your app. In fact, many of the standard system view controllers (including UIImagePickerController and ABPeoplePickerNavigationController) are navigation controllers that are specifically designed to be presented modally.

When you want to present your own custom navigation interfaces modally, you always pass the navigation controller object as the first parameter to the presentModalViewController:animated: method. You must always configure the view controller appropriately before presenting it. At a minimum, your navigation controller should have a root view controller. And if you want to start the user at a different point in the navigation hierarchy, you must add those view controllers to the navigation stack (without animations) before presenting the navigation controller.

Listing 6-2 shows an example of how to create and configure a navigation controller and display it modally. In the example, the view controllers pushed onto the navigation stack are custom objects you need to define and configure with views. And the currentViewController object is a reference to the currently visible view controller that you also need to provide.

Listing 6-2  Displaying a navigation controller modally

MyViewController1*  rootVC = [[MyViewController1 alloc] init];
MyViewController2*  nextVC = [[MyViewController2 alloc] init];
NSArray * viewControllers = [NSArray arrayWithObjects:rootVC, nextVC, nil];
 
// Create the nav controller and set the view controllers.
UINavigationController*  theNavController = [[UINavigationController alloc]
                          initWithRootViewController:rootVC];
[theNavController setViewControllers:viewControllers animated:NO];
 
// Display the nav controller modally.
[currentViewController presentModalViewController:theNavController animated:YES];

As with all other modally presented view controllers, the presenting view controller is responsible for dismissing its modally presented child view controller in response to an appropriate user action. When dismissing a navigation controller though, remember that doing so removes not only the navigation controller object but also the view controllers currently on its navigation stack. The view controllers that are not visible are simply removed from the navigation stack, but the topmost view controller also receives the usual viewWillDisappear: message.

For information on how to present view controllers (including navigation controllers) modally, see Presenting View Controllers from Other View Controllers in View Controller Programming Guide for iOS. For additional information on how to configure a navigation controller for use in your app, see Navigation Controllers.

Displaying a Tab Bar Controller Modally

It is possible (although uncommon) to present a tab bar controller modally in your app. Tab bar interfaces are normally installed in your app’s main window and updated only as needed. However, you could present a tab bar controller modally if the design of your interface seems to warrant it. For example, to toggle from your app’s primary operational mode to a completely different mode that uses a tab bar interface, you could present the secondary tab bar controller modally using a crossfade transition.

When presenting a tab bar controller modally, you always pass the tab bar controller object as the first parameter to the presentModalViewController:animated: method. The tab bar controller must already be configured before you present it. Therefore, you must create the root view controllers, configure them, and add them to the tab bar controller just as if you were installing the tab bar interface in your main window.

As with all other modally presented view controllers, the parent view controller is responsible for dismissing its modally presented child view controller in response to an appropriate user action. When dismissing a tab bar controller though, remember that doing so removes not only the tab bar controller object but also the view controllers associated with each tab. The view controllers that are not visible are simply removed, but the view controller displayed in the currently visible tab also receives the usual viewWillDisappear: message.

For information on how to present view controllers (including navigation controllers) modally, see Presenting View Controllers from Other View Controllers in View Controller Programming Guide for iOS. For information on how to configure a tab bar controller for use in your app, see Tab Bar Controllers.

Using Table View Controllers in a Navigation Interface

It is very common to combine table views with a navigation controller to create a navigation interface. Because navigation controllers facilitate the navigation of a data hierarchy, tables are often used to provide the user with the choice of where to navigate next. Tapping a row in one table takes the user to a new screen displaying the data associated with that row. For example, selecting a playlist in the iPod app takes the user to a list of songs in that playlist.

One way to manage tables is with a UITableViewController object. Although this class makes the management of tabular data much easier, you still need to implement custom code to facilitate navigation. Specifically, when the user taps a row in the table, you need to push an appropriate new view controller object onto the navigation stack.

Listing 6-3 shows an example of how to navigate to the next level of data in a navigation interface. Whenever the user taps a specific row in the current table, you use the information associated with that row to initialize a new view controller, which you then push onto the navigation stack. The initWithTable:andDataAtIndexPath: method is a custom method that you have to implement yourself; its job is to get the data object for the row and use it to initialize the next level view controller.

Listing 6-3  Navigating data using table views

// Implement something like this in your UITableViewController subclass
// or in the delegate object you use to manage your table.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
   // Create a view controller with the title as its
   // navigation title and push it.
   NSUInteger row = indexPath.row;
   if (row != NSNotFound)
   {
      // Create the view controller and initialize it with the
      // next level of data.
      MyViewController *viewController = [[MyViewController alloc]
                   initWithTable:tableView andDataAtIndexPath:indexPath];
      [[self navigationController] pushViewController:viewController
                   animated:YES];
   }
}

For more detailed information about managing tables and using table view controllers, see Table View Programming Guide for iOS.