10.9. Tab Bars

Tab bars are one of Apple's solutions for a universal device with no physical buttons. With iPhone applications so rich in features, many have four or five important functions that the user may need to get to quickly. Located at the bottom of the screen, tab bars provide what would traditionally be looked at as shortcut buttons on other mobile devices. Going back to Apple's book metaphor, tab bars are the bookmarks to different chapters of an application. They also help the developer to logically group functions within an application, to present the primary functions the application will provide.

Many of the preloaded iPhone applications use tab bars, including the Mobile Phone application, YouTube, and the iTunes WiFi Music Store. The tabs separate related pages of data (e.g., Featured Music, Purchased Songs, etc.) or provide shortcuts to different functions within a single application (e.g., Contacts, Recent Calls, Voicemail, etc.).

10.9.1. Tab Bar Controllers

In UI Kit, the UITabBar class represents tab bars and the UITabBarController class encapsulates them, which makes it easy to manage a number of different views and transitions. Like other view controllers, tab bars are designed to be autonomous in their presentation. Internally, they handle all of the mess of button selection and view transition—they "just work."

Tab bar controllers work in a similar fashion to navigation controllers: navigation controllers read the navigation bar properties of each view controller class pushed on its stack. The view controller itself defines the properties of its bar, and those are displayed when the view is displayed. Similarly, a tab bar controller allows you to add a series of view controllers to it, and each view controller identifies its own button properties, which are then displayed by the tab bar controller.

Think of this approach as mass production for a box of crayons. Each crayon you place in the box has its own pigment and label. If you printed a list of available colors on the side of the box, you'd have to change the box every time you manufactured a different set of colors. A more cost-effective way to design the box would be to place a clear plastic window on the front so the customer can see the individual colors inside the box. This lets you swap out crayons without changing the box.

Each view you want to display is like a single crayon, and the tab bar controller is the box. Rather than hardcoding the button titles and images into the tab bar controller, the tab bar controller simply arranges each view so that its own "label" (tab bar button) shows through the box. This way, the labels belong to the views instead of the controller. The tab bar controller then handles the work of transitioning to the selected view when the user presses its button.

10.9.2. Building a Tab Bar Controller

To build a tab bar controller, you'll first need the individual pages you'll want to provide buttons for. Create each page as a UIViewController object, like you've been doing since Chapter 3.

10.9.2.1. Build a collection

You may have several different types of view controllers within your application to handle various functions. If you are writing a game application, you may have controllers such as GameViewController, SettingsViewController, HighScoreViewController, and AboutViewController. Before you create the tab bar, you'll first create an array of the view controller objects you want to display in the tab bar. An example of this follows:

NSMutableArray *viewControllers = [ [ NSMutableArray alloc ] init ];
[ viewControllers addObject: myGameViewController ];
[ viewControllers addObject: mySettingsViewController ];
[ viewControllers addObject: myHighScoreViewController ];
[ viewControllers addObject: myAboutViewController ];

10.9.2.2. Configure button properties

As you learned from the crayon box illustration, each view controller comes with its own "label" defining what its tab bar button should look like. The tab bar then displays the tab bar button according to the view controller's properties. Configure the tab bar button within the view controller's init method to define its title and/or tabBarItem property:

- (id) init {
    self = [ super init ];
    if (self != nil) {
        self.tabBarItem = [ [ UITabBarItem alloc ]
            initWithTitle: @"High Scores"
            image: [ UIImage imageNamed: @"high_scores.png"
            tag: 4
        ];
    }
    return self;
}

Set the tabBarItem property to a UITabBarItem object. You can initialize tab bar items in one of two ways. The initWithTitle method, as shown in the previous example, allows you to use a title and image to render the button with user-defined data. A method also exists to create a number of system buttons—standard buttons used widely throughout many iPhone applications. To create these system buttons, use the initWithTabBarSystemItem method:

self.tabBarItem = [ [ UITabBarItem alloc ]
    initWithTabBarSystemItem: UITabBarSystemItemFavorites tag: 4
];

You may use the following system buttons identifiers:

UITabBarSystemItemMore
UITabBarSystemItemFavorites
UITabBarSystemItemFeatured
UITabBarSystemItemTopRated
UITabBarSystemItemRecents
UITabBarSystemItemContacts
UITabBarSystemItemHistory
UITabBarSystemItemBookmarks
UITabBarSystemItemSearch
UITabBarSystemItemDownloads
UITabBarSystemItemMostRecent
UITabBarSystemItemMostViewed
10.9.2.3. Creating the tab bar controller

Once each view has assigned itself a tab bar button, you can create a tab bar controller object and add the array of view controllers to it:

UITabBarController *tabBarController = [ [ UITabBarController alloc ] init ];
tabBarController.viewControllers = viewControllers;

                                          

10.9.2.4. Displaying the tab bar controller

After you have created the tab bar controller, display it by adding it as a subview to a window or another view:

[ window addSubview: tabBarController.view ];
[ window makeKeyAndVisible ];

10.9.3. Customizable Buttons

By default, a tab bar controller allows the user to customize the layout of buttons when more than five exist. This can be done by clicking the More tab, followed by an Edit button on the navigation bar. You may choose to only allow certain tabs to be customized, or you may disable all customizations. To do this, set the tab bar controller's customizableViewControllers to an array of view controllers you want to allow the user to customize:

NSMutableArray *customizableControllers = [ [ NSMutableArray alloc ] init ];
[ customizableControllers addObject: myGameViewController ];
[ customizableControllers addObject: mySettingsViewController ];
tabBarController.customizableViewControllers = customizableControllers;

                                          

Use nil to disable all customization:

tabBarController.customizableViewControllers = nil;

10.9.4. Navigation

When you display the tab bar controller, the controller handles its own navigation so that the selected tab's view is automatically transitioned to the front of the screen. If more than five view controllers are attached to a tab bar controller, the tab bar controller will automatically display a tab labeled More. When the user taps on this button, the view controllers that could not fit on the screen will be displayed in a table list form.

To read or change the currently active view controller, use the selectedViewController property:

tabBarController.selectedViewController = myGameViewController;
UIViewController *activeController = tabBarController.selectedViewController;
if (activeController == myGameViewController) {
    /* myGameViewController is currently active */
}

                                          

You can also refer to buttons by index:

tabBarController.selectedIndex = 3;

NOTE

 

If you specified more than five view controllers, the "More" view controller will be assigned its own index.

10.9.5. Delegate Actions

To be notified when a view is selected on the tab bar, assign a tab bar controller delegate:

tabBarController.delegate = self;

The delegate will be notified whenever a view controller is selected, through a call to a delegate method named didSelectViewController:

- (void)tabBarController:(UITabBarController *)tabBarController
    didSelectViewController:(UIViewController *)viewController
{
    /* Additional special code to handle selection */
}

The delegate will also be notified whenever the user has finished customizing the tab bar layout. You can receive this notification by adding a delegate method named didEndCustomizingViewControllers:

- (void)tabBarController:(UITabBarController *)tabBarController
    didEndCustomizingViewControllers:(NSArray *)viewControllers
    changed:(BOOL)changed
{
    /* Additional special code to handle end of customizing tab bar */
}

10.9.6. TabDemo: Another Textbook Approach

In Chapter 3, you were introduced to transitions by means of a page-flipping example. This example is similar, except you'll use eight pages representing eight different view controllers in an application. Each page will be controlled by a button on the tab bar which, when pressed, will flip to the corresponding page. The tab bar automatically handles the transition to the selected view controller.

You can compile this application, shown in Examples Example 10-24 through Example 10-28, with the SDK by creating a view-based application project named TabDemo. Be sure to pull out the Interface Builder code if you'd like to see how all objects are created from scratch.

Example 10-24. TabDemo application delegate prototypes (TabDemoAppDelegate.h)
#import <UIKit/UIKit.h>

@class TabDemoViewController;

@interface TabDemoAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    UITabBarController *tabBarController;
    NSMutableArray *viewControllers;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

Example 10-25. TabDemo application delegate (TabDemoAppDelegate.m)
#import "TabDemoAppDelegate.h"
#import "TabDemoViewController.h"

@implementation TabDemoAppDelegate

@synthesize window;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    CGRect screenBounds = [ [ UIScreen mainScreen ] bounds ];

    self.window = [ [ [ UIWindow alloc ] initWithFrame: screenBounds ]
        autorelease
    ];

    viewControllers = [ [ NSMutableArray alloc ] init ];
    for(int i = 0; i < 8; i ++) {
        [ viewControllers addObject: [
            [ TabDemoViewController alloc ] initWithPageNumber: i ]
         ];
    }

    tabBarController = [ [ UITabBarController alloc ] init ];
    tabBarController.viewControllers = viewControllers;

    [ window addSubview: tabBarController.view ];
    [ window makeKeyAndVisible ];
}

- (void)dealloc {
    [ window release ];
    [ super dealloc ];
}

@end

                                          

Example 10-26. TabDemo view controller prototypes (TabDemoViewController.h)
#import <UIKit/UIKit.h>

@interface TabDemoViewController : UIViewController {
    UITextView *textView;
    int page;
}
- (id) initWithPageNumber:(int)pageNumber;

@end

Example 10-27. TabDemo view controller (TabDemoViewController.m)
#import "TabDemoViewController.h"

@implementation TabDemoViewController

- (id) initWithPageNumber:(int)pageNumber {
    self = [ super init ];
    if (self != nil) {
        page = pageNumber;
        self.title = [ NSString stringWithFormat: @"Page %d", page ];
        self.tabBarItem = [ [ UITabBarItem alloc ] initWithTitle:
            [ NSString stringWithFormat: @"Page %d", page ] image: nil tag: page ];
    }
    return self;
}

- (void) loadView {
    [ super loadView ];

    CGRect bounds = [ [ UIScreen mainScreen ] applicationFrame ];
    textView = [ [ UITextView alloc ] initWithFrame: bounds ];
    textView.text = [ [ NSString alloc ]
        initWithFormat: @"Text for page %d", page
    ];

    self.view = textView;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:
    (UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
    [ super didReceiveMemoryWarning ];
}

- (void)dealloc {
    [ textView release ];
    [ super dealloc ];
}

@end

                                          

Example 10-28. TabDemo main (main.m)
#import <UIKit/UIKit.h>

int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, @"TabDemoAppDelegate");
    [pool release];
    return retVal;
}

10.9.7. What's Going On

  1. When the application is run, its application delegate creates a series of eight different view controllers and adds them to an array named viewControllers. The TabDemoViewController class uses its own initWithPageNumber method, which customizes its title, button, and display text according to the page number passed to it.

  2. A UITabBarController is created and assigned the array of eight view controllers. The tab bar controller is then added to the main window.

  3. Whenever a new tab is pressed, the tab bar controller automatically handles the transition to the new view. It also displays the More button on its own, and allows the user to customize button positions. That's it! It's that simple.

10.9.8. Further Study

Now that you've learned how to get people to press your buttons, have a little more fun with tab bars:

  • Try your hand at creating system buttons and your own custom button images.

  • Modify this demo so that when the user selects page 3, it automatically reselects page 4, just to mess with their head.

  • Check out the UITabBar.h, UITabBarController.h, and UITabBarItem.h prototypes. You'll find these deep within /Developer/Platforms/iPhoneOS.platform, inside the UI Kit framework's Headers directory.