Developing with Navigation Controllers
The UINavigationController class provides all the high-calorie goodness of a UINavigationBar-based
interface with minimal navigation-specific programming. Navigation
controllers let users move smoothly between views (or, more accurately,
view controllers) using built-in animation. They provide history control
for free without any programming effort. Navigation controllers
automatically handle Back button functionality. The titles of each
parent view controller appear as Back buttons, letting users “pop the
stack,” so to speak, without any further programming effort.
And if that
weren’t enough, the navigation controller also offers a simple menu bar.
You can add buttons—or even more complicated controls—into the bar to
build actions into your application. Between these three features of
navigation, history, and menus, navigation controllers build a lot of
wow into a simple-to-program package.
The following recipes
introduce these core navigation controller features, from building menus
to building a history stack. In these examples, you see how to use the UINavigationController class to create a variety of novel and useful interfaces.
Setting Up a Navigation Controller
Whether you
plan to use a navigation controller to simplify moving between views—its
intended use—or use it as a convenient menu button holder you should
understand how the navigation controller works. At their simplest level,
navigation controllers manage view controller stacks.
Every
navigation controller owns a root view controller. This controller
forms the base of the stack. You can programmatically push other
controllers onto the stack. This extends the navigation breadcrumb trail
and automatically builds a Back button each time a new view controller
gets pushed.
Tap one of these Back
buttons to pop controllers off the stack. Users can pop back until
reaching the root. Then you can go no further. The root is the root, and
you cannot pop beyond that root.
This stack-based design lingers even when you plan to use just one view controller. You might want to leverage the UINavigationController’s
built-in navigation bar to build a two-button menu, for example. This
would disregard any navigational advantage of the stack. You still need
to set that one controller as the root via initWithRootViewController:.
You can use Interface Builder and Xcode templates to build navigation-based interfaces, or you can create those same interfaces by
hand. The easiest way to do so is by building your navigation controller
in the applicationDidFinishLaunching:
method that gets called at the start of your application run. Here, you
set up the window, create the navigation controller, and assign its
root.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
UIWindow *window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
UINavigationController *nav = [[UINavigationController alloc]
initWithRootViewController:[[HelloWorldController alloc]
init]];
[window addSubview:nav.view];
[window makeKeyAndVisible];
}
This is one of the few places you don’t really have to worry about memory management and leaky calls. An application delegate’s dealloc
method is never called at application termination, so while you can
assign the window and the navigation controller to instance variables
and use those variables in a deallocation method, it doesn’t really
matter if you’d rather not do so.
Pushing and Popping View Controllers
Add new items onto the navigation stack by pushing a new controller with pushViewController: animated:. Send this call to the navigation controller that owns a UIViewController. This is normally called on self.navigationController. When pushed, the new controller slides onscreen from the right (assuming you set animated to YES).
A left-pointing Back button appears, leading you one step back on the
stack. The Back button uses the title of the previous view controller.
There are many reasons
you’d push a new view. Typically, these involve navigating to subviews
like detail views or drilling down a file structure. You can push
controllers onto the navigation controller stack after your user taps a button, a table item, or a disclosure accessory.
Perform push requests and navigation bar customization (like setting up a bar’s right-hand button) inside UIViewController subclasses. As a rule, there’s no reason or need to ever subclass UINavigationController.
And, for the most part, you need never access the navigation controller
directly. The two exceptions to this rule include managing the
navigation bar’s buttons and when you change the bar’s look.
You might change a bar style or its tint color by accessing the navigationBar property directly.
self.navigationController.navigationBar.barStyle =
UIBarStyleBlackTranslucent;
To add a new button you modify your navigationItem,
which provides an abstract class that describes the content shown on
the navigation bar. To remove a button, assign the item to nil.
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]
initWithTitle:@"Action" style:UIBarButtonItemStylePlain target:self
action:@selector(performAction:)] autorelease];
The Navigation Item Class
The objects that populate the navigation bar are put into place using the UINavigationItem
class, which is an abstract class that stores information about those
objects. Navigation item properties include the left and right bar
button items, the title shown on the bar, the view used to show the
title, and any Back button used to navigate back from the current view.
This class basically
enables you to attach buttons, text, and other UI objects into three key
locations: the left, the center, and the right of the navigation bar.
Typically, this works out to be a regular button on the right, some text
(usually the UIViewController’s title) in
the middle, and a Back-styled button on the left. But you’re not
limited to that layout. You can add custom controls to any of these
three locations You can build navigation bars with search fields,
segment controls, toolbars, pictures, and more.
You’ve already seen how
to add custom bar button items to the left and right of a navigation
item. Adding a custom view to the title is just as simple. Instead of
adding a control, assign a view. This code adds a custom UILabel, but this could be a UIImageView, a UISwitch, or anything else.
self.navigationItem.titleView = [[[UILabel alloc]
initWithFrame:CGRectMake(0.0f,0.0f, 120.0f, 36.0f)] autorelease];
The simplest way to customize the actual title is to use the title property of the child view controller rather than the navigation item.
When
you want the title to automatically reflect the name of the running
application, here is a little trick you can use. This returns the short
display name defined in the bundle’s Info.plist file.
self.title = [[[NSBundle mainBundle] infoDictionary]
objectForKey:@"CFBundleName"];
Modal Presentation
With normal navigation
controllers, you push your way along views, stopping occasionally to pop
back to previous views. That approach assumes that you’re drilling your
way up and down a set of data that matches the tree-based view
structure you’re using. Modal presentation offers another way to show a
view controller. After sending the presentModalViewController: animated: message, a new view controller slides up into the screen and takes control until it’s dismissed with dismissModalViewControllerAnimated:. This enables you to add special-purpose dialogs into your applications that go beyond alert views.
Typically, modal
controllers are used to pick data such as contacts from the Address Book
or photos from the Library, but you can use modal controllers in any
setting where it makes sense to perform a task that lies outside the
normal scope of the active view controller.
You can present a modal dialog in any of three ways, controlled by the modalTransitionStyle property of the presented view controller. The standard, UIModalTransitionStyleCoverVertical, slides the modal view up and over the current view controller. When dismissed it slides back down. UIModalTransitionStyleFlipHorizontal
performs a back-to-front flip from right-to-left. It looks as if you’re
revealing the back side of the currently presented view. When
dismissed, it flips back left-to-right. The final style is UIModalTransitionStyleCrossDissolve. It fades the new view in over the previous one. On dismissal, it fades back to the original view.
Utility Function
showAlert() function acts as a visual version of NSLog(),
and it displays a message and information about where the call
originated. This function can be called using the same parameters as
NSLog, complete with format string and arguments. For space
considerations, this alert code is not listed in individual recipes.
Invoking the alert code is shown in Figure 1.
#define showAlert(format, ...) myShowAlert(__LINE__, (char *)__FUNCTION__, format, ##__VA_ARGS__)
// Simple Alert Utility
void myShowAlert(int line, char *functname, id formatstring,...)
{
va_list arglist;
if (!formatstring) return;
va_start(arglist, formatstring);
id outstring = [[[NSString alloc] initWithFormat:formatstring
arguments:arglist] autorelease];
va_end(arglist);
NSString *filename = [[NSString stringWithCString:__FILE__]
lastPathComponent];
NSString *debugInfo = [NSString stringWithFormat:@"%@:%d\n%s",
filename, line, functname];
UIAlertView *av = [[[UIAlertView alloc] initWithTitle:outstring
message:debugInfo delegate:nil
cancelButtonTitle:@"OK"otherButtonTitles:nil] autorelease];
[av show];
}