Learning iOS Development: A Hands-on Guide to the Fundamentals of iOS Programming (2014)

Chapter 7. Navigation Controllers I: Hierarchies and Tabs

Most apps have a main view leading to multiple screens, often with more than one way to move through them. You need to provide a way for your users to move around your app and some way for them to know where they are. Navigation controllers provide different ways to move between the parts of an app and the functionality those parts provide.

On iPhones and iPod touches, most apps using navigation controllers fall into two types. One uses a single main view that navigates through a series of screens showing progressively more or less detail. Contacts, Notes, and Settings are good examples. Others, such as Clock and Music, have several main views, each with different functionality or even their own hierarchy of content.

iOS provides controllers for managing navigation through each type of content. UINavigationController is for moving up and down hierarchies of information, and UITabBarController handles switching between multiple main views.

In this chapter, you explore both navigation controllers: what they are for, when to use them, and how to use them. You start by adding a UINavigationController to the CarValet app and connecting up the add/view and edit car scenes. You also use a toolbar and some color to add more polish to your app.

Next you work with UITabBarController for navigating among the car hierarchy, car images, and an About view. While doing this, you add tab bar items and hook them to the scenes they control. You also create and integrate a screen without using the storyboard.

As a result of all your work in this chapter, you will be able to use two powerful navigation controllers in your apps. You will know when and where to use them and how to make them work, as well as some more ways to make your apps stand out.

Navigation Controller

Think about using the Settings app. Better still, open it up and explore a little. You can lay out the content of the Settings app as an inverted tree, with the main screen at the top, and all the content screens as the leaves. Figure 7-1 shows a partial hierarchy with the leaf nodes in italic.

Image

Figure 7-1 Partial settings hierarchy

This is a very common way to organize your content on iPhones and iPod touches. It elegantly solves how to show lots of content in a small space. Settings lets you modify hundreds of options for your iPhone and the apps it contains. Using a hierarchy to “fold” the content works well with both the screen size and how people are generally good at categorization and grouping.

Apps using hierarchies of content work in basically the same way. There is a top-level screen with an overview of the content, and then there are screens with more detail. The Settings app is a good example. You start at the top level with all the possible categories of settings, and you keep drilling down until you reach the detail for a particular setting.

UINavigationController is built to handle this type of navigation. It keeps track of the customer as he or she navigates the hierarchy of screens and provides default controls to get back. The controller does this by embedding all your content inside itself.

Figure 7-2 shows the parts of a navigation controller. At the top is a navigation bar. This is typically used for context and unwinding the current path. The title usually orients the user to what he or she is viewing. As you will see shortly, a left-hand back button enables the user to go back one step in the navigation path. An app can also have custom buttons on the navigation bar.

Image

Figure 7-2 Navigation controller

At the bottom is an optional toolbar that gives you another space to put controls, in this case an action button. Your view controller goes in the gray area. Somewhere in the current scene, you provide a way to open the next level of detail. In the add/view scene, for example, this is the Edit button.

When you use a navigation controller, you start by connecting it to the first, or root, scene (a view controller). When the navigation controller opens, it opens this root scene. As you segue to a new scene (view controller), the navigation controller puts it on the current path and displays the scene.

The path is tracked as a stack of view controllers, with the root view on the bottom. Figure 7-3 shows the stack as you navigate to the Hebrew keyboard screen in Settings. You can see a back button in the navigation bar in each screen after the root view. The default is a less than symbol (<) followed by the name of the previous screen. If the name is too long, the screen name is replaced by the word “Back.”

Image

Figure 7-3 Navigation stack for license in settings

Navigation Controller Classes

All navigation functionality is provided through five main classes:

Image UINavigationController coordinates all the components for the current navigation stack. In addition to managing the navigation stack and all transitions, it has references to the navigation bar and the optional toolbar.

Image UINavigationBar is a view for the navigation bar at the top of the screen. It is used by UINavigationController to show the title of the current scene along with a button to return to the previous screen if needed. It can be used on its own, though that is not recommended.

Image UINavigationItem manages the UINavigationBar for a particular view controller in the hierarchy. There are properties and methods for setting a title, and showing, adding, and hiding bar button items, including the back button.

Image UIBarButtonItem provides an object for managing buttons in navigation and toolbars. You can create everything from system buttons such as Done, Cancel, and Action to text buttons, and more. Button items are not UIButtons or even a type of view. They are management items that can display something in the user interface. You can put a UIButton inside a button item.

Image UIToolbar manages an optional toolbar, usually at the bottom of a screen. There are methods and properties for setting button items and appearance as well as a way to specify if it is placed at the bottom or top of the screen.

UINavigationItem uses the title property of the current view controller to set the text. Changing the title changes the text. You saw this earlier when you localized scene titles.

Figure 7-4 shows the navigation controller–related objects in the add/view scene of CarValet selected on the left-hand Interface Builder (IB) view element browser. The UINavigationController in the browser as well as on the left of the canvas includes the shared navigation bar.

Image

Figure 7-4 Navigation Controller items in the add/view scene in CarValet

The navigation controller manages the add/view scene in addition to others. Each managed scene has a UINavigationItem for setting the content of the navigation bar, including a title. The add/view title bar also has a bar button item to show the car images scene.

The scene has a special segue identifier for the first, or root view, controller as shown in Figure 7-4. Unlike push segues, this one looks like a line with two circles on each end. The root scene is the first scene presented by the navigation controller. The other connections on the storyboard show navigation paths between scenes. As an example, there is a connection between the add/view and edit scenes. Because there is nowhere else to go except back, the edit scene is a leaf node, just like those in Figure 7-1.

Adding a Toolbar

Toolbars can give a more balanced feel to an app and do not need to be in every scene. A natural choice is to move the navigation and Edit buttons in the add/view scene into a toolbar.

The first thing is to add some icons to your project asset library. You can start with either the CH07 CarValet Starter project provided with the samples or continue to use your project from the previous chapter. Follow these steps:

1. Use the project navigator to select the Images.xcassets item.

2. Click the + button in the lower left of the image asset area to show the add assets popup, as shown in Figure 7-5.

Image

Figure 7-5 Asset catalog and asset addition popup

3. Choose Import from the popup. In the file dialog that appears, select the CH07 Assets Toolbar Icons folder that comes as part of the sample code for this chapter and add it to the catalog.

When you press OK, the contents of the folder in the Finder are copied to the asset catalog. A new asset folder is created with the same name and is selected, showing the new icons. Your catalog should now look like Figure 7-6.

Image

Figure 7-6 Asset catalog: imported items

The catalog provides a lot of flexibility for your application images and icons. You can see at a glance if you have both retina (2x) and nonretina (1x) images. You can also slice images, something that is very useful if the image has to fill an unknown area.

Although slicing is not a part of this book, the basic idea is to designate which parts of your image can be duplicated and which ones are stable. A typical example is a solid background image with rounded corners. The rounded corners and edges stay static, but the middle can grow as much as it needs.


Tip: Finding Icons

Most great-looking apps have great-looking graphics. If you’re not a graphic designer, it’s a good idea to get help from those who are. There are several sources of icons and other elements on the web, as a search for “iOS icon graphics” shows. Be sure to take a good look before you buy.

The button graphics used previously as well as other custom graphical elements you use later were kindly provided by Joseph Wain, one of those good designers. You can download his icons, both free and paid, at http://glyphish.com. Joseph also does custom work.


Populating the Toolbar

Now that you have the icons, it is time to add a toolbar to the add/view car screen. An important thing to remember is where you add the toolbar. You can always drag a UIToolbar into any view controller. But for toolbars that could be project wide, you set them up in the navigation controller. Here’s how you do this:

1. Open the iPhone storyboard and select the Navigation Controller.

2. In the Attributes inspector, change Bottom Bar from Inferred to Opaque Toolbar. A toolbar appears at the bottom of the controller as well as of every scene on the storyboard.

3. Select the add/view car scene and drag three UIBarButtonItems into the toolbar. Then drag in a flexible space item between each pair of buttons. Figure 7-7 shows adding the second flexible space.

Image

Figure 7-7 Adding a flexible space

4. Select the middle bar button item, delete the title, and use the Attributes inspector to set the identifier to Edit.

5. Select the left bar button item, delete the title, and set the image to arrow-left and the button style to plain. As you start typing the name of the image into the field, Xcode uses autocomplete to show possible images.

Do the same thing with the right-hand button, using arrow-right. These new icons from Glyphish give the app a more professional look.

6. Delete the original Previous, Next, and Edit buttons and constrain the bottom of the Car Info label flush with the bottom of the view car area.

7. Ctrl-drag from the new Previous and Next buttons to the view controller and select the same messages the original buttons sent. Note you can Ctrl-drag to the round yellow controller icon in the black bar below the scene on the canvas. This is faster than dragging to the left-hand list next to the canvas.

8. When you deleted the Edit button in step 6, you also deleted the segue to the edit scene. Add it back by Ctrl-dragging from the new Edit button to the edit scene and set up a push. Select the segue and set the identifier to EditSegue, the same as the segue using the original Edit button.

Run the app in the simulator. Try adding a few cars and make sure the new Previous and Next buttons work. Now edit the car, and you will see a small problem. The toolbar shows up in the editor. In fact, it shows up in all scenes.

For some apps, you want a toolbar in some scenes but not in others. Luckily, there is an easy way to manage which scenes show toolbars and which do not. The toolbarHidden property of UINavigationController determines if the toolbar is shown (value is NO) or not (value isYES). When you add a toolbar using IB, the default is to show the toolbar in every scene—that is, toolbarHidden for the navigation controller is set to NO. This is different from adding one programmatically, as the documentation states the default value is YES, or hidden.

To make sure everything works as expected, add a line at the top of viewDidLoad, just after the call to super in each of the view controllers as follows:

Image In ViewController.m, make sure the toolbar shows by adding:

self.navigationController.toolbarHidden = NO;

Image In CarEditViewController.m and CarImageViewController, add the following:

self.navigationController.toolbarHidden = YES;

Run the app again and make sure the toolbar is only on the main screen.

Revisiting Localization

Now that the new toolbar buttons are working, you no longer need to localize their titles, nor do you need a reference to the Edit button. Make the following changes:

1. In viewDidLoad in ViewController.m, remove the lines reading and setting localized titles for the Previous, Next, and Edit car buttons. You can find the code just after the Add car button is localized.

2. Open ViewController.h, and remove the line with the editCarButton property.

3. Change the type of the previousCarButton and nextCarButton properties to UIBarButtonItem.

4. Open an IB canvas showing the main scene. Add an Assistant editor showing ViewController.h and connect the two modified properties with the new bar button items.

Before leaving the toolbar, you need to do a small update to localization. Run the app in Arabic using the AppleLanguages flag you set in Chapter 5, “Localization,” in the section, “Adding Arabic Strings.” Use the shortcut of Option-clicking the Run button to quickly modify the scheme.

The Arabic screen appears to be fine. All the text is still correctly aligned and the arrows appear to point the correct way. In fact, they do point the correct way, but the behavior is reversed. In Arabic, as in other right-to-left languages, the left-pointing arrow goes to the next item, not the previous one. The right-pointing arrow goes back.

The graphics are correct, but the behaviors are reversed. (See “Caution: Using Arrows in Buttons” for a variation of the same kind of problem.) Once again you can use the system language direction to fix the issue.

Replace the previousCar: and nextCar: action methods in ViewController.m with the code from Listing 7-1. Note that you could also abstract behavior into changedDisplayedCar:, but you would then need to make sure the method is only called from user actions.

Listing 7-1 Updating previousCar: and nextCar: for Right-to-Left Languages


- (IBAction)previousCar:(id)sender {
    NSInteger indexShift = -1;                                              // 1

    NSLocaleLanguageDirection langDirection;                                // 2
    langDirection = [NSLocale characterDirectionForLanguage:
                     [NSLocale preferredLanguages][0]];

    if (langDirection == NSLocaleLanguageDirectionRightToLeft) {
        indexShift = 1;                                                     // 3
    }

    [self changeDisplayedCar:displayedCarIndex + indexShift];               // 4
}

- (IBAction)nextCar:(id)sender {
    NSInteger indexShift = 1;

    NSLocaleLanguageDirection langDirection;
    langDirection = [NSLocale characterDirectionForLanguage:
                     [NSLocale preferredLanguages][0]];

    if (langDirection == NSLocaleLanguageDirectionRightToLeft) {
        indexShift = -1;
    }

    [self changeDisplayedCar:displayedCarIndex + indexShift];
}


Here’s what happens in the numbered lines in Listing 7-1:

1. Use a variable for the amount to change the index. Default to minus-one, or go backward for previous and plus-one, or go forward for next.

2. Get the current language direction.

3. If the language direction is right-to-left, reverse the send of the index change variable.

4. Call changeDisplayedCar: using the current index plus the localized index change.

Run the Arabic localization again and confirm the arrow toolbar buttons work correctly. Also note that the Edit button is localized. This is because you are using the system Edit button.


Caution: Using Arrows in Buttons

Using the arrows in the toolbar resulted in one type of issue: correct images but reversed behaviors.

You can also run into the opposite problem. Imagine going back to the localization chapter before you added the toolbar. Now replace the Previous and Next button titles with the arrow images.

Because the buttons use trailing and leading for alignment, running in Arabic correctly reverses the buttons. The button on the left goes to the next car, and on the right-hand one goes back. However, the graphics are reversed. The button on the left of the Arabic screen is the next button, the one with the right-pointing arrow.

In a case like this, you have to reverse the graphics, not the behaviors.


Message-Based Navigation

So far you have used segues to navigate between scenes, including using the special exit segue. You have some control of passing data and modifying behaviors using prepareForSegue:sender:. Using UINavigationController properties and messages, you have full control. You can do things like push a new view controller to the stack, pop the current view controller, pop to the root-view controller, retrieve the array of view controllers for the current navigation stack, and even rearrange the array of view controllers.

You can use code-based messages to add an about scene to the CarValet app. Instead of adding another scene to the storyboard, you create the interface in a separate user interface resource file. In Xcode, these are called XIB files.

You can tell the navigation controller to open an XIB file by sending a message. Here’s how you do all this:

1. Add a new Objective-C UIViewController subclass called AboutViewController and select the With XIB for Interface check box. Move the three new files to the CarValet group, just above Supporting Files.

2. Select AboutViewController.xib, and something that looks like the Storyboard editor opens. This is IB, without any support for storyboard functionality, such as segues and connections. Use the Attributes inspector for the view to set the top bar to an opaque navigation bar. You cannot set the title by double-clicking in the navigation bar.

3. Add some content to the view using labels, perhaps “CarValet, brought to you by <insert your name here>” and any other words of wisdom that take your fancy. In a shipping app, this would be a place to list your company and contact info, copyrights, support information, legal terms, and other related info. In fact, it might be just another step in the hierarchy of views such as About in Settings.

4. Open the iPhone storyboard, select the CarValet view controller, and drag a bar button item onto the left side of the button bar and set the title to “About.”

5. Use the Assistant editor to Ctrl-drag the aboutCarValet: action from the new About button item to ViewController.h.

6. Open ViewController.m in the editor and import AboutViewController.h. Now fill out the body of the aboutCarValet: method with the code in Listing 7-2.

Listing 7-2 aboutCarValet:


- (IBAction)aboutCarValet:(id)sender {
    AboutViewController *nextController;

    nextController = [[AboutViewController alloc]                        // 1
                      initWithNibName:@"AboutViewController"
                      bundle:[NSBundle mainBundle]];

    nextController.title = @"About CarValet";                            // 2

    [self.navigationController pushViewController:nextController         // 3
                                         animated:YES];
}


Here’s what happens in the numbered lines in Listing 7-2:

1. Initialize the next view controller to an instance of AboutViewController. This call uses the XIB you created earlier for the view controller’s interface.

2. Set the title of the About view. You could localize this string.

3. Tell the navigation controller to push the new view controller on the stack—that is, to open and transition to the scene.

Run the app making sure not to use Arabic; tap the Info button in the upper left and the About view opens. The navigation controller takes care of opening the instance of AboutViewController, animating in the new scene, and adding a back button to the navigation bar. Also note the toolbar shows up. Add the appropriate code to hide the toolbar for the About view.

A Bit of Color

As you look at the attributes of some items in IB, you sometimes see a Tint option. Using this option is a quick way to lend a colored theme to your user experience. Note that this is different from changing the Background color in the View area.

For UINavigationController, you can change the tint of the navigation bar, toolbar, and bar button items. Setting the tint of something that is managed by a navigation controller changes the color of that thing for every screen managed by that controller.

For instance, you are about to set the tint of the app’s navigation bar. This one change in tint changes the navigation bar color for the entire hierarchy. This is because the navigation controller manages the navigation bar. The scenes shown by the navigation controller do not manage the bar.

A car app probably brings some colors to mind—perhaps a racy red, a cool midnight blue, or some other color. Whatever the color, change the theme for the navigation bar and toolbar using these steps:

1. Open the iPhone storyboard and expand the navigation controller in the left-hand column.

2. Select the Navigation Bar item and open the Attributes inspector. Look for a color picker labeled “Bar Tint” in the Navigation Bar section.

3. Change the tint to the color of your choice. Figure 7-8 uses Sky from the crayon palette accessed from the Other menu item in the popup. Notice as you change the tint in the navigation controller that it changes the tint for every navigation bar and any bar buttons.

Image

Figure 7-8 Adding tint to the Navigation Bar

4. Change the bar tint for the toolbar in the same way: Select the Toolbar item, which is usually below the Navigation Bar selected in step 2. You can use the same color as in step 3 or a different one, though the same color is better for a balanced look.


Note: In Case Toolbar Bar Tint Does Not Work

In the pre-release version of Xcode used for this book, setting the tint color of a toolbar using IB did not work. If that is still the case, you can set it by adding the following code at the top of viewDidLoad just below the call to set toolbarHidden:

UIColor *sky = [UIColor colorWithRed:102.0/255.0
                               green:204.0/255.0
                                blue:255.0/255.0
                               alpha:1.0];
self.navigationController.toolbar.barTintColor = sky;

This sets the bar tint color to an RGB value. The method used to generate the color requires values from 0 to 1, so each RGB value is divided by 255.


Run the app, and you see that all the navigation bars and the toolbars are tinted with the new color, though the buttons and titles no longer look good.

One use of colors for bars, buttons, and titles is to give your app a consistent feel. Ideally, the colors should match some sort of branding. Doing this requires designing a color scheme, something best done by professionals. Buttons need to be noticeable as they are how the user performs actions, but not so noticeable that they overpower the content. Bars and titles should fade into the background.

Table 7-1 shows the color scheme used for the CarValet in this book. The colors are shown as both Apple Crayon colors and RGB (Red/Green/Blue) values.

Image

Table 7-1 CarValet Color Scheme

First, change the navigation bar title color. You do this with the same Attributes inspector you used to change the navigation bar tint. Further down, you see a Title Color attribute. Change that to white.

You could go through the app and select each button and bar button and change their colors, but that is subject to a lot of error. Instead, use the appearance protocol, a special addition to some classes that lets you modify how they look throughout your app.

All you need is a few lines of code.

Open AppDelegate.m. In the method application:didFinishLaunchingWithOptions:, replace the body of the method with the code in Listing 7-3. Note that the colors look different on the simulator and on a real device. The simulator colors depend on what monitor you are using. Always test color choices on real devices. It is the only way to know what they will actually look like.

Listing 7-3 Changing the Text Color of All UIButtons and UIBarButtonItems


UIColor *mocha = [UIColor colorWithRed:128.0/255.0 green:64.0/255.0         // 1
                                  blue:0.0 alpha:1.0];


[[UIButton appearance] setTitleColor:mocha forState:UIControlStateNormal];  // 2
[[UIBarButtonItem appearance] setTintColor:mocha];                          // 3

return YES;


Here’s what happens in the numbered lines in Listing 7-3:

1. Set up a temporary mocha color using the Crayons palette—after all, we all need a good coffee sometimes.

2. Use the Appearance protocol to set all button title tint colors to mocha in their normal state.

3. Use the same protocol to set the tint color for all bar button item titles.

You can use the appearance protocol to set defaults for a lot more than button colors. You can use it for the navigation bar tint, background images for buttons and other items, and a lot more. If you want to know more, the Apple documentation on UIAppearance is a good place to start.

Before you go to the next section, save a copy of the current implementation. It is used as the base of two challenges at the end of the chapter.

Tab Bar Controller

A navigation controller is good for moving through related scenes. But what if you have different functionality or different categories of scenes? For that, you want a navigation controller that switches between different kinds of content. This is what UITabBarController provides.

A good example is the Clock app, shown in Figure 7-9. On the bottom of the app is a bar with four buttons, the tab bar. Each tab shows a different major function of Clock. Three of those areas have at least two screens: World Clock has a screen to add or edit a city, Alarm has one for adding or editing the details for an alarm, and Timer lets you specify a sound.

Image

Figure 7-9 Clock app

Each of the four areas has a different layout and performs different functions, though all have a similar color and font scheme.

How the Tab Bar Works

With UITabBarController, each functional area or tab is a root view. In effect, the tab bar controller is the real root view of the application, as you can see in Figure 7-10.

Image

Figure 7-10 Clock hierarchy

When the user taps an item in the tab bar, the root-level view for that item is shown, even if it is the currently selected tab bar item. For example, on your own device, open Music, select Playlists, select a playlist, and then tap the Playlists tab at the bottom. The app returns to the list of all playlists.

Figure 7-11 shows the parts of a tab bar controller. This tab bar, shown in white, has two items: the first with a circle icon and the second with a square. Each tab shows a different root-level controller view. The tab bar controller contains the current root-view controller along with the tab bar. The tab bar contains the bar items and manages changing between root-level views.

Image

Figure 7-11 Tab bar controller

There is no navigation bar for a title or buttons. Adding one can be as simple as using a UINavigationController for the root view of a particular tab, as two of the Clock tab items do. It is also possible to add a navigation bar manually, though then you need to add the methods and protocols to manage it.

A tab bar controller uses three main classes:

Image UITabBarController manages the view controllers shown by the items in the tab bar. It also manages user interaction with the bar, including handling selection. If there are more than five items, it includes a More button. It also manages accessing the extra tab buttons and rearranging the order of the tabs.

Image UITabBar is a view for presenting one to five tab bar items. Each item represents a different root view for the app. If there are more than five items, a special More item is included on the right.

Image UITabBarItem is an individual item in the tab bar and includes properties for both the title and image, as well as properties to customize how the image behaves during selection.

CarValet: Adding a Tab Bar

For a storyboard, you add a tab bar controller the same way you add a navigation controller: Select the root-view controller or controllers and then select Editor > Embed In > Tab Bar Controller. The selected view controllers all have to be at the same level of an application. This means you cannot select both a navigation controller, such as the add/view scene controller, and a subview of that controller, such as the car image scene controller.

Next, you add a tab bar controller to the CarValet app and include tabs for the add/view, car image, and about scenes. First, add the tab bar controller:

1. Add the CH07 Assets TabBar Icons folder that comes as part of the code for Chapter 7 to the Images.xcassets.

2. Open the iPhone storyboard and select the top-level navigation controller.

3. Choose Editor > Embed In > Tab Bar Controller. You now have a tab bar controller with one item.

4. In the browser, expand the new tab bar controller and open the Attributes editor for the tab bar. Change the image tint and bar tint to be consistent with the current colors in your app.

5. Expand the navigation controller in the browser and select the Tab Bar Item. Change the Title to Add/View and set Image to car, as shown in Figure 7-12.

Image

Figure 7-12 Setting the navigation controller tab bar item to car

Run the app in the simulator. At the moment, it is not very interesting, as there is only one choice. Since the root-view controller of the first item is a navigation controller, all the navigational elements are there, and navigation through the app works.

Moving Car Images to the Tab Bar

The next part of integrating the tab bar is removing scenes from the navigation controller and adding them to the tab bar controller. Follow these steps to add the car images scene as the second tab item:

1. Remove the Car Images button from the add/view scene. When you do this, you also remove both the segue to the images scene and the navigation bar from the images scene.

2. Ctrl-drag a connection from the tab bar controller to the car images view controller. In the popup that appears, select view controllers from the Relationship Segue category.

This tells the tab bar that the chosen view controller is one of the items in the tab bar.

3. Set the title of the tab bar item in the car image view controller to Car Images and the Image to photo.

After you do this, notice a yellow constraint problem indicator in the left-hand column. This makes sense because the car number label used to be constrained to the navigation bar, and that is gone. To fix this, follow these steps:

1. Choose the label, and then choose Update Frames from the constraint fixing popup.

2. Choose the label, and then choose Clear constraints for the constraint fixing popup. This is the item next to the pin constraint popup.

3. Now use the pin constraint popup to set the constraints to 15 from the nearest top and bottom view and the default distance from the leading edge.

Run the app and tap the Car Images tab bar item. You are taken to the images scene, and all functionality still works. However, there is no way to reset the zoom. Follow these steps to add a graphical Reset Zoom button:

1. Open CarImageViewController.h in the editor and change the type of resetZoomButton from UIBarButtonItem to UIButton.

2. In the iPhone storyboard, select the CarImageViewController and drag a normal button into the top-level view and change the title to Reset Zoom.

3. Constrain the new button to the system distance from the top and trailing edges of the superview.

4. Drag a connection from the button to the resetZoomButton instance variable in the .h file.

5. Drag a connection from the button to the car image view controller to send the resetZoom: message.

6. In the Control section of the Attributes editor for the new button, set it to be disabled by default.

Run the app and go to the images screen. This time there is a Reset Zoom button. When the images screen first opens, the text is light gray and the button cannot be pressed. When you zoom the car, the text color changes, and the button is enabled. Resetting the zoom or manually changing the zoom back to 1.0 disables the button.

Car Valet: Moving Info

As with the navigation controller, you can modify the behaviors of a toolbar by using methods and properties. You can change the order of tab items, change the tab items, and find or set the currently selected tab.

In this section, you create a tab for the About view. Note that you could do all this in the storyboard, but going through the following steps gives you a chance to dynamically update the tab bar items:

1. Remove the About button from the navigation items in the main add/view scene by selecting the bar button item in IB or the browser and deleting it.

2. Open ViewController.m and remove the import of AboutViewController.h. Also remove the aboutCarValet: method implementation from the file, as well as the declaration from the .h file.

3. Open AppDelegate.m and import AboutViewController.h. Then add the bold code in Listing 7-4 to the beginning of application:didFinishLaunchingWithOptions:.

Listing 7-4 New application:didFinishLaunchingWithOptions:


- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UITabBarController *tabBarController =
                        (UITabBarController*)self.window.rootViewController; // 1

    AboutViewController *aboutViewController =                               // 2
                            [[AboutViewController alloc]
                             initWithNibName:@"AboutViewController"
                             bundle:[NSBundle mainBundle]];

    UITabBarItem *aboutItem = [[UITabBarItem alloc]                          // 3
                               initWithTitle:@"About"
                               image:[UIImage imageNamed:@"info"]
                               tag:0];
    [aboutViewController setTabBarItem:aboutItem];                           // 4

    NSMutableArray *currentItems = [NSMutableArray                           // 5
                        arrayWithArray:tabBarController.viewControllers];

    [currentItems addObject:aboutViewController];                            // 6
    [tabBarController setViewControllers:currentItems animated:NO];          // 7
...


Here’s what happens in the numbered lines in Listing 7-4:

1. Get a reference to the tab bar controller. Since Main_iPhone is set as the main storyboard for the project, the tab bar is already set as the root-view controller of the window by this point in the app life cycle.

2. Create an about view controller from the XIB file.

3. Create a tab bar item for the about scene, and give it an appropriate title and image.

4. Set the tabBarItem of the about view controller to the new tab bar item. The tab bar controller looks for this property when setting up the items in the tab bar.

5. Create a mutable array based on the view controllers managed by the tab bar controller. In this case, they are the ones you set up on the storyboard.

6. Add the about scene to the end of the view controller array. The items in the tab bar are displayed in the same order as in the array.

7. Update the array of items with no animation as the app is launching. If you do this while the app is running based on some user action, you are likely to animate changes.

Run the app in the simulator. There are now three tab bar items. The About item brings up the about view controller.

Summary

The controllers you have explored in this chapter make your app behave and look more like an iOS app. With UINavigationController, you added hierarchical navigation to viewing and editing cars. After you added the controller to the scene, hooked up the hierarchy, and set some properties, the navigation controller handled the extra interface elements and movement between screens.

You used UITabBarController to group related sets of content and made moving between content easy. It took just a bit of work in IB and a few lines of code. You also built a view controller outside the storyboard, using both IB and code. Then you integrated it into the flow of your app.

In addition, you used tinting to add more differentiation to your app, added a toolbar with buttons, and learned how to add graphical assets to your app.

Now you can add both kinds of navigation controllers to your own apps. You know how to design with and for them, as well as how to take a design to reality. And when you use some public domain code (source code for solving problems that others make available for general use) with a controller, you can integrate the screen into your app. You can even make your apps more colorful.

Although your app looks closer to an iOS app, there is still something missing. The add/view car scene still seems a bit clunky. It was great for learning basics, but a shipping-ready app really needs to show a table. And that is what Chapter 8, “Table Views I: The Basics,” is all about.

Challenges

1. Implement the about scene using the Storyboard editor in both the navigation controller and tab bar controller cases. In addition to creating an about view controller on the storyboard, you need to remove all the code used and manually create it. You no longer need the XIB file, though you can quickly copy and paste the contents to the newly created storyboard view controller. Remember to set the class of the view controller you drag onto the storyboard to AboutViewController.

2. Add code to the UINavigationController-based implementation to show the last top-level scene the user was in. For example, if the user was in the car images scene, took the app out of memory, and then launched the app, the app shows the car images scene, not the add/view scene. To solve this challenge, you need to do the following:

Image Implement a way to identify what scene is active when applicationWillTerminate: is called and save that information. The easiest thing to do is set up a number where 0 is add/view, 1 is edit, 2 is car images, and 3 is about. Save that to the user preferences with the following code:

// get the standard user defaults for this app
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

// set the preference for current scene
// (assumes currSceneType is set elsewhere to the id for the scene)
[defaults setInteger:currSceneType forKey:@"SceneType"];

// make sure to synchronize so the defaults are saved
[defaults synchronize];

Image Read the saved value in application:didFinishLaunchingWithOptions:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

// Get the last viewed scene (0 if there was none)
NSInteger lastScene = [defaults integerForKey:@"SceneType"] ;

Image Set the navigation view controller stack as needed. You only really need to set the stack if it is the car image or About view. In that case, the image or About view would be on top, and the add/view scene would be on the bottom. You can use setViewControllers:animated:to do this.

3. Add code to the tab bar–based implementation to restore the last viewed tab if the user removes the app from memory and launches it. The code to save and reload a preference is the same as that in Challenge 2. You can check or set the currently selected item of a tab bar controller by using the selectedIndex property.