Combined View Controller Interfaces

The UIKit framework provides only a handful of standard view controllers for implementing your application interface:

You can use these view controllers singly or in conjunction with other view controllers to create even more sophisticated interfaces. When combining view controllers, however, the order of items in the preceding list is significant. The general rule is that each view controller can incorporate the view controllers preceding it in the list. Thus, a navigation controller can incorporate custom view controllers, and a tab bar controller can incorporate both navigation controllers and custom view controllers. However, a navigation controller should not incorporate a tab bar controller as part of its navigation interface. The resulting interface would be confusing to users because the tab bar would not be consistently visible.

Because modal view controllers represent an interruption of sorts anyway, 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 new interface style supplants the style of its parent, albeit only temporarily.

The following sections show you how to combine table view, navigation and tab bar controllers in your iOS applications. For more information on using split view controllers in iPad applications, see iPad-Specific Controllers.

Adding a Navigation Controller to a Tab Bar Interface

An application 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. You never want to push a tab bar controller onto the navigation stack of a navigation controller. Doing so creates an unusual situation whereby the tab bar appears only while a specific view controller is at the top of the navigation stack. Tab bars are designed to be persistent, and so this transient approach can be confusing to users.

The most common way to use a tab bar controller is to embed its view in your application’s main window. Therefore, the following sections show you how to configure your application’s main window to include a tab bar controller and one or more navigation controllers. Examples are included for doing this both programmatically and using Interface Builder. If you need to present a tab bar controller modally, it is generally recommended that you create the relevant objects programmatically.

Creating the Objects in Interface Builder

The process for combining tab bar and navigation controllers in a nib file is relatively straightforward. The only real difference is how you create the relationship between the tab bar controller and the navigation controller. When using these objects by themselves, each one takes on the role of the root view for the application’s window. When combined, however, only the tab bar controller assumes this role. Instead of providing the root view for the window, the navigation controller acts as the root view for a tab in the tab bar interface.

Figure 7-1 shows the configuration of the objects that you need to create in your nib file. In this example, the first three tabs of the tab bar interface use custom view controllers but the last tab uses a navigation controller. One additional view controller is then added to act as the navigation controller’s root view controller. To manage memory better, each of the custom view controllers (including the root view controller of the navigation controller) stores its corresponding view in a different nib file.

Figure 7-1  Mixing navigation and tab bar controllers in a nib file

Assuming you are starting from a generic main nib file (one that does not include a tab bar controller), you would use the following steps to create the objects from Figure 7-1 in Interface Builder:

  1. Drag a tab bar controller object from the library to your Interface Builder document window.

    When you add a tab bar controller to a nib file, Interface Builder also adds a tab bar view, two root view controllers, and two tab bar items (one for each view controller).

  2. Save a reference to the tab bar controller using an outlet.

    In order to access the tab bar controller at runtime, you either need to use an outlet or you must explicitly retrieve the nib file’s top-level objects when you load the nib file. Using an outlet is generally much simpler. To add an outlet for both the tab bar controller and window, you need to include code similar to the following in your application delegate’s header file:

    @interface MyAppDelegate : NSObject <UIApplicationDelegate> {
       UITabBarController*  tabBarController;
       UIWindow *window;
    }
    @property (nonatomic, retain) IBOutlet UIWindow *window;
    @property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
    @end

    After adding the outlet definition, create a connection from that outlet to the tab bar controller object.

  3. Synthesize the properties from the preceding step by adding the following code to the implementation file of your application delegate class:

    @synthesize window;
    @synthesize tabBarController;
  4. Add one View Controller object and one Navigation Controller object to the tab bar controller.

    The number of view controllers embedded inside your tab bar controller object determines the number of tabs displayed by your tab bar interface. Because the initial tab bar controller already has two generic view controllers, you need to add one more View Controller object (UIViewController) and one Navigation Controller object (UINavigationController).

    To add a view controller, do one of the following:

    • Drag the appropriate object from the library to the tab bar in the edit surface.

    • Drag the object from the library to the tab bar controller in your Interface Builder document window. The window must be in outline mode.

    When adding the navigation controller, you should either drag the appropriate object from the library or select the tab bar controller object and configure your view controller types using the Attributes inspector. Both of these options add the correct type of view controller object to your nib file. You should never add a navigation controller by dragging a generic View Controller object to the nib file and change its class name to the desired class type.

    To delete a view controller, select the view controller object in the edit surface or document window and press the Delete key.

  5. Arrange the view controllers in the order you want them to appear in the tab bar interface.

    You can rearrange view controllers (and the corresponding tabs) by dragging the tabs displayed on the tab bar controller edit surface or by dragging the view controllers in the Interface Builder document window (outline mode only). Although the edit surface shows all of the tabs, only five are displayed at runtime. If your tab bar controller contains six or more view controllers, only the first four are displayed in the tab bar initially. The last spot on the tab bar is reserved for the More view controller, which presents the remaining view controllers.

  6. Configure the view controllers.

    For each root view controller, you should configure the following attributes:

    • Set the class of each custom view controller object using the Identity inspector. For generic View Controller objects, change the class name to the custom subclass you want to use to display the contents of that tab. Do not change the class of the Navigation Controller object itself but do set the class of the navigation controller’s embedded custom view controller.

    • Provide a view for each custom view controller. The preferred way to do this is to configure the NIB Name attribute of each custom view controller with the name of the nib file containing the view. Although you can include each view in the same nib file as the view controller, doing so is not recommended. For information on how to configure the nib file for a custom view controller, see Storing the View in a Detached Nib File.

    • As appropriate, configure any style or appearance information for any of the view controllers.

  7. Configure the tab bar item for each view controller.

    You can select the tab bar item from the tab bar controller edit surface or from the Interface Builder document window when it is in outline or browser modes. Using Interface Builder, you can specify the title, image, and badge of a tab bar item. Alternatively, you can set the tab bar item to one of the standard system tabs by assigning a value to the Identifier property in the Attributes inspector.

  8. Save your nib file.

Although the preceding steps configure the tab bar interface, they do not install it in your application’s main window. To do that, you need to add some code to the applicationDidFinishLaunching: method of your application delegate, as shown in Listing 7-1. This is where you use the outlet you created for the tab bar controller in your application delegate.

Listing 7-1  Installing the combined interface in your application’s window

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    [window addSubview:tabBarController.view];
}

Creating the Objects Programmatically

If you are creating a combined tab bar and navigation interface programmatically for your application’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.

  6. Add the tab bar controller’s view to your application’s main window.

Listing 4-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 would provide yourself. And the init method of each custom view controller is also a method that you would provide to initialize the view controllers. The tabBarController and window variables are declared properties of the class that retain their values.

Listing 7-2  Creating a tab bar controller from scratch

- (void)applicationDidFinishLaunching:(UIApplication *)application {
   self.tabBarController = [[[UITabBarController alloc] init] autorelease];
 
   MyViewController1* vc1 = [[[MyViewController1 alloc] init] autorelease];
   MyViewController2* vc2 = [[[MyViewController2 alloc] init] autorelease];
   MyViewController3* vc3 = [[[MyViewController3 alloc] init] autorelease];
   MyNavRootViewController* vc4 = [[[MyNavRootViewController alloc] init] autorelease];
   UINavigationController* navController = [[[UINavigationController alloc]
                           initWithRootViewController:vc4] autorelease];
 
   NSArray* controllers = [NSArray arrayWithObjects:vc1, vc2, vc3, navController, nil];
   tabBarController.viewControllers = controllers;
 
   // Add the tab bar controller's current view as a subview of the window
   window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
   [window addSubview:tabBarController.view];
}

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 the View for Your View Controller.

Displaying a Navigation Controller Modally

It is perfectly reasonable (and relatively common) to present navigation controllers modally from your application. In fact, many of the standard system view controllers (including UIImagePickerController and ABPeoplePickerNavigationController) are navigation controllers that were 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 7-3 shows an example of how you would 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 would need to define and configure with views. And the currentViewController object is a reference to the currently visible view controller that you would also need to provide.

Listing 7-3  Displaying a navigation controller modally

MyViewController1*  rootVC = [[MyViewController1 alloc] init];
MyViewController2*  nextVC = [[MyViewController2 alloc] init];
 
// Create the nav controller and add the view controllers.
UINavigationController*  theNavController = [[UINavigationController alloc]
                          initWithRootViewController:rootVC];
[theNavController pushViewController:nextVC animated:NO];
 
// Display the nav controller modally.
[currentViewController presentModalViewController:theNavController animated:YES];
 
// Release the view controllers to prevent over-retention.
[rootVC release];
[nextVC release];
[theNavController release];

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 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 released, 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 a View Controller Modally. For additional information on how to configure a navigation controller for use in your application, see Creating a Navigation Interface.

Displaying a Tab Bar Controller Modally

It is possible (albeit uncommon) to present a tab bar controller modally in your application. Tab bar interfaces are normally installed in your application’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 application’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. This means that 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 released 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 a View Controller Modally. For information on how to configure a tab bar controller programmatically, see Creating a Tab Bar Interface Programmatically.

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 application takes the user to a list of songs in that playlist.

When it comes to managing tables, one way to do so 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 7-4 shows an example of how you would navigate to the next level of data in a navigation interface. Whenever the user taps a specific row in the current table, you would use the information associated with that row to initialize a new view controller, which you would then push onto the navigation stack. The initWithTable:andDataAtIndexPath: method is a custom method that you would have to implement yourself; its job would be to get the data object for the row and use it to initialize the next level view controller.

Listing 7-4  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.