Home / iPhone Tips and Tutorials

Navigation Controller

In the first project, we create an application that presents a series of three scenes through a navigation controller. Within each scene, we show a Push button that increments a counter and then transitions to the next scene. The counter will be stored in a custom subclass of the navigation controller. In other words, this will provide both an example of building a navigation-based UI and of using the navigation controller to manage a property that all the scenes can access.

Implementation Overview

The implementation follows the process described earlier. We start with a Single View Application template, remove the initial scene and view controller, and then add a navigation controller and two custom classes: one a subclass of a navigation controller that will enable each scene in the application to share information, the other a subclass of a view controller that will handle user interactions in the scenes.

We add two additional scenes beyond the default root scene added with the navigation controller. A Push button is included in each scene's view with an action method to increment a counter-as well as a segue from that button to the next scene.

Doesn't each scene need its own view controller subclass? Yes and no. In most applications, you create a view controller for each scene. In this application, we're doing the same thing in each scene (so that a single view controller can be used, saving us time and code).

Setting Up the Project

Create a new single-view project called LetsNavigate. Before doing anything else, clean up the project so that we only have the things that we need. Start by selecting the ViewController class files (ViewController.h and ViewController.m) and pressing the Delete key. When prompted, choose to delete the files, not just the references.

Next, click the MainStoryboard.storyboard file and then select the View Controller line in the Document Outline area (Editor, Show Document Outline) and again press Delete. The scene will disappear. We now have the perfect starting point for our app.

Adding the Navigation Controller and Generic View Controller Classes

We need two additional classes added to the project. The first, a subclass of UINavigationController, will manage our push count property and be named CountingNavigationController. The second, a subclass of UIViewController, will be named GenericViewController and will handle incrementing the push count as well as displaying the count in each scene.

Click the + button at the bottom-left corner of the Project Navigator. Choose the iOS Cocoa Touch category and the UIViewController subclass, and then click Next. Name the new class CountingNavigationController, set the subclass to UINavigationController (you will have to type the class name in), and click Next. On the last setup screen, choose your main project code group from the Group pop-up menu, and then click Create.

Repeat this process to create a new UIViewController subclass named GenericView Controller. Make sure you choose the right subclass for each of the new classes; otherwise, you'll have difficulty later on.

Adding the Navigation Controller

Open the MainStoryboard.storyboard in the Interface Builder Editor. Display the Object Library (Control+Option+Command+3) and drag a navigation controller into an empty area of the Interface Builder Editor (or into the Document Outline area). Your project will now show a navigation controller scene and a root view controller scene. For now, concentrate on the navigation controller scene.

We want to associate this controller with our CountingNavigationController class, so select the Navigation Controller line in the Document Outline, and then open the Identity Inspector (Option+Command+3). From the class drop-down menu, choose CountingNavigationController.

Now let's add the additional scenes we need and associate them with the generic view controller class we created.

Adding Additional Scenes and Associating the View Controller

With the storyboard still open, drag two instances of the view controller object from the Object Library into the editor or the Document Outline. In a few minutes, these will be connected to the root view controller scene to form a series of scenes that are managed by the navigation controller.

After adding the additional scenes, there are two things we will want to do to each of them (including the root view controller scene). First, we need to set the identity of each scene's view controller. In this case, one view controller class is handling all of them, so the identity will be set to GenericViewController. Next, it's a good idea to set a label for each view controller so that the scene has a friendlier name.

Start by selecting the root view controller scene's view controller object and opening the Identity Inspector (Option+Command+3). Use the Class drop-down menu to pick the GenericViewController. Still within the Identity Inspector, set the Label field to First. Move to one of the new scenes you added, select its view controller line, set its class to GenericViewController, and the label to Second. Repeat the process for the last scene as well, setting its custom class and a label of Third.

Planning the Variables and Connections

There isn't a great deal of information that needs to be stored or actions that will have to be defined. The CountingNavigationController will have a single property (pushCount) that contains the number of times we've pushed a new scene into view using the navigation controller.

The GenericViewController class will have a single property called countLabel that references a label in the UI displaying the current count of pushes. It will also have an action method named incrementCount that increases the pushCount property in the CountingNavgationController by one.

The outlet and action in the GenericViewController class will be defined once, but, to be used in each scene, must be connected to the label and button individually in each scene.

Creating the Push Segues

It becomes very helpful to lay them out in the storyboard, with segues, so you can see how it all fits together before you create the interface. In addition, with the navigation controller and tab bar controllers, creating the connection actually adds objects to our scenes that we might want to configure when working on the interface, so, in my opinion, it just makes sense to create the segues first.

To build a segue for the navigation controller, we need something to trigger it. Within the Storyboard Editor, add a button (UIButton) labeled Push to the first and second scenes, but not the third. Why not the third? Because it is the last scene that can be displayed; there's nothing after it to segue to.

Next, Control-drag from the button in the first scene to the second scene's view controller line in the Document Outline, or target the scene directly in the editor. When prompted for the segue type, choose Push. A new segue line (Segue from UIButton to Second) will be added to the first scene in the Document Outline, and the second scene will inherit the navigation controller's navigation bar, as well as gain a navigation item in its view.

Repeat this process, creating a push segue from the second scene's button to the third scene. Your Interface Builder Editor should now contain a fully realized navigation controller sequence. Click and drag each scene in the view to arrange it in a way the makes sense to you.

Designing the Interface

By adding the scenes and buttons, you've really just built most of the interface. The final steps will be customizing the title of the navigation item in each scene and adding an output label to display the push count.

Begin by going through each of the scenes (first, second, and third) and doubleclicking the center of the navigation bar that now appears at the top of each view. Title the first view First Scene, the second Second Scene, and the third... wait for it... Third Scene.

Finally, to each of the scenes add a label (UILabel) near the top that reads Push Count: and a second label (the output label) with the default text of 0 (and a large, center aligned font, if you like) to the center of each view.

Creating and Connecting the Outlets and Actions

There is only one outlet and one action that need to be defined in this project, but they need to be connected several times. The outlet (a connection to the label displaying the push count, countLabel) will be connected to each of the three scenes.

The action (incrementCount) will only need to be connected to the button in the first and second scenes.

Position your display in the Interface Builder Editor so that the first scene is visible (or just use the Document Outline), and click its push count label, and then switch to the Assistant Editor mode.

Adding the Outlet

Control-drag from the label in the center of the first scene to just below the @interface line in GenericViewController.h. When prompted, create a new outlet named countLabel.

That created the outlet and the connection from the first scene; now you need to connect it to the other two scenes. Control-drag from the second scene's push count label and target the countLabel property you just created. The entire line will highlight, showing you are making a connection to an existing outlet. Repeat this for the third scene, connecting its push count label to the same property.

Adding the Action

Adding and connecting the action works in much the same way. Start by Controldragging from the first scene's button to just below the property definition in GenericViewController.h. When prompted, create a new action named incrementCount. Switch to the second view controller and Control-drag from its button to the existing incrementCount action. You've just made all the connections we need.

Implementing the Application Logic

Most of our work is now behind us. To finish the tutorial, we first need to set up the pushCount property in the CountingNavigationController class so that it can keep track of the number of times we've pushed a new scene in the application.

Adding the Push Count Property

Open the CountingNavigationController.h interface file and add a property definition for an integer named pushCount below the @interface line:

@property (nonatomic) int pushCount;

Next, open the CountingNavigationController.m file and add a corresponding @synthesize statement below the existing @implementation line:

@synthesize pushCount;

That's all we need to do to implement the custom CountingNavigationController class. Because it is a subclass of a UINavigationController, it already performs all the navigation controller tasks we need, and now it stores a pushCount property, too.

To access this property from the GenericViewController class that is handling the content for all the scenes in the application, we need to import the custom navigation controller's interface file in GenericViewController.h. Add this line following the existing #import statement:

#import "CountingNavigationController.h"

We're all set to finish our implementation, which is just a matter of adding the logic to GenericViewController that increments the counter and makes sure it is displayed on the screen when a new scene is pushed into view.

Incrementing and Displaying the Counter

To increment the counter in GenericViewController.m, we use the parentViewController property to access the pushCount property. The parentViewController, as you've learned, is automatically set to the navigation controller object within any scene managed by the navigation controller.

We need to typecast the parentViewController to our custom class of CountingNavigationController, but the full implementation is just a single line. Implement incrementCount as shown in Listing-1.

LISTING-1 The incrementCount Implementation

- (IBAction)incrementCount:(id)sender {
    ((CountingNavigationController *)self.parentViewController).pushCount++;
}

The final step is to update the display to show the current count. Since pushing the button increments the push count and pushes a new scene into view, the incrementCount action isn't necessarily the best place for this logic to fall. In fact, it won't always be accurate, because the count could be updated in another view, and then the Back button used to "pop" back to the original view, which would now be showing an invalid count.

To get around this, we simply add the display logic to the viewWillAppear:animated method. This method is called right before a view is displayed onscreen (regardless of whether it is through a segue or by a user touching the "back" button), so it is a perfect place to update the label. Add the code in Listing-2 to the GenericViewController.m file.

LISTING-2 Update the Display in viewWillAppear:animated

1: -(void)viewWillAppear:(BOOL)animated {
2:     NSString *pushText;
3:     pushText=[[NSString alloc] initWithFormat:@"%d",
4: 		((CountingNavigationController *)
5: 		self.parentViewController).pushCount];
6:     self.countLabel.text=pushText;
7: }

Line 2 declares a new string, pushText, that will contain a string representation of the counter. Line 3 allocates and initializes this string using the NSString initWithFormat method. The "%d" format string is replaced by the contents of the pushCount property, accessed using the same approach as in the incrementCount method.

In the last step, line 6, the countLabel is updated with the pushText string.

Building the Application

Run the application and test the navigation controller. Use the button to push new scenes on to the navigation controller stack, and then pop them back off with the Back button functionality that we get for free. The push count will stay in sync through all the different scenes because we now have a central class (CountingViewControllert) managing our shared property for us.

[Previous] [Contents] [Next]