Other View Controllers - Learning iPhone Programming (2010)

Learning iPhone Programming (2010)

Chapter 6. Other View Controllers

Now that we’ve discussed the UITableView and UINavigationController (as well as their associated classes and views) and built an iPhone application using them, you’ve actually come a long way toward being able to write applications on your own. With these classes under your belt, you have the tools to attack a large slice of the problem space that iPhone applications normally address.

In this chapter, we’ll look at some of the other view controllers and classes that will be useful when building your applications: simple two-screen views (utility applications), single-screen tabbed views (tab bar applications), a view controller that takes over the whole screen until dismissed (modal view controller), and a view controller for selecting video and images (image picker view controller).

Utility Applications

Utility applications perform simple tasks: they have a one-page main view and another window that is brought into view with a flip animation. The Stocks and Weather applications that ship with the iPhone and iPod touch are examples of applications that use this pattern. Both are optimized for simple tasks that require the absolute minimum of user interaction. Such applications are usually designed to display a simple list in the main view, with preferences and option settings on the flip view. You access the flip view by clicking a small i icon from the main view.

The Xcode Utility Application template implements the main view and gives the user access to a flipside view. It is one of the most extensive templates in Xcode and it implements a fully working utility application, which is fortunate as the documentation Apple provides regarding this type of application is otherwise somewhat lacking in details.

Open Xcode and start a new project. Click Application under the iPhone OS group, and then select Utility Application from the New Project window as the template (see Figure 6-1). Click Choose, and name the project “BatteryMonitor” when requested.

Selecting Utility Application in the New Project window

Figure 6-1. Selecting Utility Application in the New Project window

Figure 6-2 shows the Project window in Xcode and lists the files the template generates. The names of the classes the template generates are meant to hint strongly at what each of them does, but if not, Xcode has conveniently put the relevant classes into groups. Since the template implements all the logic necessary to control the application’s interface, we only need to implement our own UI and some basic logic to control it.

Click Build and Run to compile and run the application. You’ll find that it’s a fully working utility application, although with blank main and flipside views.

The new Utility Application project open in Xcode

Figure 6-2. The new Utility Application project open in Xcode

Making the Battery Monitoring Application

The somewhat descriptive name of the application has probably revealed its purpose already. We’re going to implement a simple battery monitoring application, and to do so I’m going to introduce you to the UIDevice class. This is a singleton class that provides information relating to your hardware device. From it you can obtain information about your device such as its unique ID, assigned name, device model, and operating system name and version. More importantly, you can also use the class to detect changes in the device’s characteristics such as physical orientation, and register for notifications about when these characteristics change.

Note

A singleton class is restricted in some manner such that only one instance of the class can be created. This design pattern can be used to coordinate actions or information across your application. Although some argue that because use of singleton classes introduces global state into your application, and is therefore almost by definition a bad thing, I think that when it is used correctly the pattern can simplify your architecture considerably.

Information—and notifications—about the device battery state weren’t introduced until the 3.0 update of the SDK. Even now the implementation is somewhat coarse-grained (notifications regarding charge level changes occur in only 5% increments).

Warning

The UIDevice class has several limitations, and some developers have resorted to the underlying IOKit framework to obtain more information about the device (e.g., better precision to your battery measurements). However, while Apple marked the IOKit as a public framework, no documentation or header files are associated with it.

If you use this framework and try to publish your application on the App Store, it is possible that Apple will reject it for using a private framework despite its apparent public status. In the official documentation, IOKit is described as “Contain[ing] interfaces used by the device. Do not include this framework directly.”

Building our interface

First we’re going to build our interface. Double-click on the MainView.xib file (located in the Resources group in the Groups & Files pane) to open it in Interface Builder. You’ll see that the default view that Xcode generated already has the Info button to switch between the main and flipside views. Not only is it there, but it’s connected to the template code, so it’s already working.

The UI will consist of just three UILabel elements, so drag and drop three labels from the Library (⌘-Shift-L, then choose Cocoa Touch→Inputs & Values) onto the Main View window, and position them roughly as shown in Figure 6-3.

The Main View NIB file being edited in Interface Builder

Figure 6-3. The Main View NIB file being edited in Interface Builder

You can use the Attributes Inspector (⌘-1) to change the font size and color as I have done with my view. We’ll be setting the text of the labels from inside our code, but for now I’ve added placeholder text (“100%”, “State:”, and “Unknown”) using the Attributes tab so that I can position the labels more neatly and get a better idea of how my UI will look.

That’s all we’re going to do to the main view. Save the file and return to Xcode. Open the FlipsideView.xib file. You’ll see that this time the default view that Xcode generates already has a navigation bar and a Done button present and connected to the template code. You need to add a label (UILabel) and switch (UISwitch) to this interface, as shown in Figure 6-4.

The flipside view being edited in Interface Builder

Figure 6-4. The flipside view being edited in Interface Builder

Drag and drop the two elements from the Library window (⌘-Shift-L, then choose Cocoa Touch→Inputs & Values) into the Flipside View window and position them as shown in Figure 6-4. Set the text of the label to “Monitor Battery”, and using the Attributes pane of the Inspector window set the label text color to white. The default black text won’t show up well against the dark gray background of the view. That’s all that’s needed. Save the file in Interface Builder, and open the BatteryMonitorAppDelegate.h file in Xcode (it’s in the Application Delegate folder in the Groups & Files pane).

Writing the code

In the interface, we need to add a Boolean variable that stores the flag that indicates whether the app is currently monitoring the battery state. Add the following inside the @interface declaration:

BOOL monitorBattery;

We also need to make this a property. Add the following next to the existing @property declarations (but before the @end):

@property (nonatomic) BOOL monitorBattery;

This means that in the BatteryMonitorAppDelegate.m implementation file, we also need to synthesize the property to create the accessor methods. Open that file and add the following next to the existing @synthesize statements:

@synthesize monitorBattery;

By default, we’re going to make it so that the application starts with battery monitoring turned off, so in the applicationDidFinishLaunching: method we must set the flag to NO. Add the following to the top of the method:

self.monitorBattery = NO;1

1

Note that we access the variable by using the accessor method generated by the @synthesize statement. It’s important to realize that accessing the instance variable directly using monitorBattery and accessing the property via a call to self.monitorBattery: are completely different in Objective-C since you are sending a message when you invoke the property, rather than directly accessing the variable.

Next, open the FlipSideViewController.h interface file (you can find it in the Flipside View folder in the Groups & Files pane) and add the lines shown in bold:

@class BatteryMonitorAppDelegate;1

@interface FlipsideViewController : UIViewController {

id <FlipsideViewControllerDelegate> delegate;

BatteryMonitorAppDelegate *appDelegate;2

IBOutlet UISwitch *toggleSwitch;3

}

1

This is a forward declaration of the BatteryMonitorAppDelegate class, which allows you to refer to it elsewhere in this file.

2

We’re going to be using the switch (UISwitch) we added to the NIB file to toggle battery monitoring on and off. Because we’re storing the state of that switch in the application delegate, we need this variable so that we can refer to the application delegate.

3

Finally, we need to add a UISwitch variable and mark it as an IBOutlet for Interface Builder.

In the FlipSideViewController.m implementation file, you first need to import the application delegate header file (using the @class forward declaration in the interface file does not remove the need to #import this header file). See Chapter 4 for details on the #import statement. Add the following line to the top of FlipSideViewController.m:

#import "BatteryMonitorAppDelegate.h"

Next, make the changes shown in bold to the viewDidLoad: method:

- (void)viewDidLoad {

[super viewDidLoad];

self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];

self.title = @"Preferences";1

appDelegate = (BatteryMonitorAppDelegate *)

[[UIApplication sharedApplication] delegate]; 2

toggleSwitch.on = appDelegate.monitorBattery; 3

}

1

This sets the title of the view.

2

We grab a reference to the application delegate here.

3

Here, we set the status of the toggle switch to reflect whether we’re currently monitoring the battery.

Now modify the done: method to save the status of the toggle switch back to the application delegate when you close the flipside view:

- (IBAction)done {

appDelegate.monitorBattery = toggleSwitch.on;

[self.delegate flipsideViewControllerDidFinish:self];

}

Finally, add the following code to the dealloc: method (this releases the toggleS⁠witch variable):

- (void)dealloc {

[toggleSwitch release];

[super dealloc];

}

The modifications we need to make to the main view controller are a bit more extensive than those we’ve made thus far. Open the MainViewController.h interface file in Xcode and make the changes shown in bold. You can find this file in the Main View folder of the Groups & Files pane.

#import "FlipsideViewController.h"

@class BatteryMonitorAppDelegate; 1

@interface MainViewController : UIViewController

<FlipsideViewControllerDelegate>

{

BatteryMonitorAppDelegate *appDelegate;2

IBOutlet UILabel *levelLabel; 3

IBOutlet UILabel *stateLabel;

}

- (IBAction)showInfo;

- (void)batteryChanged:(NSNotification *)note;4

- (NSString *)batteryLevel;5

- (NSString *)batteryState:(UIDeviceBatteryState )batteryState;6

@end

1

This is a forward declaration of the BatteryMonitorAppDelegate class, which allows you to refer to it elsewhere in this file.

2

This is a reference to the application delegate.

3

Here, we’ve added an IBOutlet for each of the two labels in the main view that we’re going to be updating: one for the battery charge level and the other for the current charging state.

4

This method will be called when we receive a notification that there has been a change in the state of the battery.

5

This is a convenience method to wrap the call to UIDevice to query the current battery level and return an NSString that we can use for the text of one of the UILabels.

6

This is another convenience method to convert a UIDeviceBatteryState into an NSString that we can use for the text of one of the other UILabels.

Save the interface file, and then open the MainViewController.m implementation file in Xcode. We declared a reference to the application delegate in the interface file, so now we need to import the relevant header file. Add this line at the top:

#import "BatteryMonitorAppDelegate.h"

We also need to grab a reference to the application delegate in the viewDidLoad: method. Uncomment the method (remove the lines that consist solely of /* and */ immediately before and after the method) and add the lines shown in bold:

- (void)viewDidLoad {

[super viewDidLoad];

appDelegate = (BatteryMonitorAppDelegate *)

[[UIApplication sharedApplication] delegate];

}

Next, we need to implement the viewWillAppear: method. At this point, you may be wondering what the difference is between this method and the previous viewDidLoad: method. The answer is that they’re called at different times: viewWillAppear: will be called each time the view becomes visible, while viewDidLoad: is called only when the view is first loaded. Because the changes we make to the preferences (on the flip side) affect the main view, we need to use viewWillAppear:, which is triggered each time we flip back from the preferences view to the main view. Add the following to MainViewController.m:

- (void)viewWillAppear:(BOOL)animated {

UIDevice *device = [UIDevice currentDevice];

device.batteryMonitoringEnabled = appDelegate.monitorBattery; 1

if (device.batteryMonitoringEnabled) {2

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(batteryChanged:)

name:@"UIDeviceBatteryLevelDidChangeNotification" object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(batteryChanged:)

name:@"UIDeviceBatteryStateDidChangeNotification" object:nil];

} else {3

[[NSNotificationCenter defaultCenter] removeObserver:self

name:@"UIDeviceBatteryLevelDidChangeNotification" object:nil];

[[NSNotificationCenter defaultCenter] removeObserver:self

name:@"UIDeviceBatteryStateDidChangeNotification" object:nil];

}

levelLabel.text = [self batteryLevel];4

stateLabel.text = [self batteryState:device.batteryState];

[super viewWillAppear:animated];

}

1

This sets the current battery monitoring state in the singleton UIDevice object to correspond to our current battery monitoring state, as determined by the switch on the flipside view.

2

If battery monitoring is enabled, we’re going to add our object as an observer to receive notifications when either the battery level or the battery state changes. If either of these events occurs, the batteryChanged: method will be called.

3

If battery monitoring is disabled, we’re going to remove the object as an observer for these notifications.

4

In either case, we’ll populate the text of our two UILabels using the convenience methods (batteryState: and batteryLevel:, which we’ll define shortly).

Since the object may be registered as an observer when we deallocate this view, we also need to make sure we remove ourselves as an observer of any notifications in the dealloc: method. Add the lines shown in bold to the dealloc: method:

- (void)dealloc {

[[NSNotificationCenter defaultCenter] removeObserver:self];

[levelLabel release];

[stateLabel release];

[super dealloc];

}

We also need to implement the batteryChanged: method; this method is called when our application is notified of a change in battery state. Here, all we’re doing is updating the text of our two labels when we receive a notification of a change. Add the following toMainViewController.m:

- (void)batteryChanged:(NSNotification *)note {

UIDevice *device = [UIDevice currentDevice];

levelLabel.text = [self batteryLevel];

stateLabel.text = [self batteryState:device.batteryState];

}

Finally, we need to implement those convenience methods. Add the following to MainViewController.m:

- (NSString *)batteryLevel {

UIDevice *device = [UIDevice currentDevice];

NSString *levelString = nil;

float level = device.batteryLevel;

if ( level == -1 ) {

levelString = @"---%";

} else {

int percent = (int) (level * 100);

levelString = [NSString stringWithFormat:@"%i%%", percent];

}

return levelString;

}

- (NSString *)batteryState:(UIDeviceBatteryState )batteryState {

NSString *state = nil;

switch (batteryState) {

case UIDeviceBatteryStateUnknown:

state = @"Unknown";

break;

case UIDeviceBatteryStateUnplugged:

state = @"Unplugged";

break;

case UIDeviceBatteryStateCharging:

state = @"Charging";

break;

case UIDeviceBatteryStateFull:

state = @"Full";

break;

default:

state = @"Undefined";

break;

}

return state;

}

We’re done in Xcode; let’s go back into Interface Builder to make all the necessary connections. Locate FlipsideView.xib under Resources in the Groups & Files pane and double-click it to open it in Xcode.

Wiring the application in Interface Builder

In the FlipsideView.xib file we need to make only one connection: between the toggleS⁠witch outlet and the UISwitch. To make the connection, click File’s Owner in the FlipsideView.xib window, and then drag the toggleSwitch outlet from the Connections Inspector (⌘-2) to the switch, as shown in Figure 6-5.

Connecting the toggleSwitch outlet to the UISwitch classwiring toggle switch in Interface BuilderUISwitch in the flipside view

Figure 6-5. Connecting the toggleSwitch outlet to the UISwitch in the flipside view

Save the FlipsideView.xib file and open the MainView.xib file. This time we need to make two connections. Just as you did in FlipsideView.xib, select File’s Owner and use the Connections Inspector to make connections between the levelLabel and stateLabel outlets and their corresponding UILabel in the main view, as shown in Figure 6-6.

Connecting the two outlets in our code to the UILabels in the main view

Figure 6-6. Connecting the two outlets in our code to the UILabels in the main view

At this point, we’re done. We’ve implemented everything we need to in code, and we’ve linked all of our outlets to our interface. Unfortunately, since this application makes use of the UIDevice battery monitoring API, and iPhone Simulator doesn’t have a battery, we’re going to have to test it directly on the device. We covered deploying applications onto your iPhone or iPod touch at the end of Chapter 3.

To deploy the application onto your device, you need to edit the Bundle Identifier inside the BatteryMonitor-Info.plist file to something appropriate, and you need to set the Code Signing Identity associated with the project in the Project Info window (although in most cases Xcode will select an appropriate signing identity automatically, if you have more than one developer profile installed this isn’t guaranteed). For more information, see Putting the Application on Your iPhone in Chapter 3. Once this is done, change the Active SDK in the overview window to “iPhone Device” and click Build and Run. Xcode should compile and deploy the application onto your iPhone.

Click the Info button in the bottom-lefthand corner to switch to the flip side and enable battery monitoring in the preferences pane. Click the Done button and return to the main view. Both the battery level and the state should have changed. While the battery level only changes every 5%, you can get some immediate feedback by plugging and unplugging your device from your Mac. The state should change from “Full” or “Charging” (see Figure 6-7) to “Unplugged”.

The main view and the flipside view of the Battery Monitor application

Figure 6-7. The main view and the flipside view of the Battery Monitor application

Tab Bar Applications

If you need to provide a number of different views on the same data set, or separately present a number of different tasks relating to your application, Apple recommends using a tab bar application. Both the iTunes and the App Store applications that ship with the iPhone and iPod touch are examples of applications that use this pattern.

To create a tab bar application, open Xcode and start a new project. Select Tab Bar Application from the New Project window as the template and name it “TabExample” when requested.

Unlike some of the other application templates provided by Apple, there are actually several different approaches you can take to building a tab bar application: loading a tab’s view from a secondary NIB, managing the tab’s view entirely from a view controller, or using a hybrid of these two approaches.

The default template provides a tab bar application with two tab items, but the way the view is managed for each of these items is very different. Double-click MainWindow.xib (it’s in the Resources group) to open it in Interface Builder. Next, make sure MainWindow.xib is the foremost window and switch to List Mode (⌘-Option-2), then fully expand Tab Bar Controller by Option-clicking the disclosure triangle to its left.

In Figure 6-8 you can see that under the Tab Bar Controller entry is the tab bar itself, and then two view controllers, each with a tab bar item.

The MainWindow.xib file generated by Xcode as part of the Tab Bar Application template

Figure 6-8. The MainWindow.xib file generated by Xcode as part of the Tab Bar Application template

Notice that the “Selected View Controller (Second)” view controller has type UIViewController. Select it and open the Attributes Inspector window (⌘-1) and you’ll also see that it loads its view from the SecondView.xib file that Xcode generated when you created the new project.

However, the “First View Controller (First)” entry is of type FirstViewController rather than UIViewController. The view here is managed slightly differently. It has no linked NIB file, and the custom view controller manages its own view.

The template generated by Apple therefore illustrates two very different ways to manage views inside a tab bar application. However, I recommend that you use neither of these two. Instead, I usually approach view management in a slightly different manner: by using a custom view controller class to manage the view, but storing the view outside the MainWindow.xib in a separate NIB file.

Refactoring the Template

Let’s refactor the current template to reflect my prejudices. Don’t worry if you think one of the other approaches sounds better; you should learn enough while refactoring the template to manage your views in either of the other two ways.

Creating the first tab

In Xcode, create a new View NIB called FirstView.xib. Then back in Interface Builder (MainWindow.xib, not the newly created NIB) click on the UIView managed by “First View Controller (First)” and press the Backspace key to delete it. This will also delete its children (a label and text view).

Next, click on “First View Controller (First)” and navigate to the Attributes Inspector (⌘-1). Using the NIB Name drop down, select your newly created FirstView NIB from the list of NIB files in the project.

Note

To add a new View XIB to the project, right-click or Ctrl-click on the Resources group in the lefthand pane in Xcode and select Add→New File. When the New File window opens select User Interface from under iPhone OS, then choose View XIB and click Next. Enter the name for the new NIB and then click Finish.

Now open the FirstView.xib file and click File’s Owner in the main window. Then, using the Identity Inspector (⌘-4), change the Class identity of File’s Owner from NSObject to UIViewController. Next, use the Connections Inspector (⌘-2) to connect the view outlet to the view inFirstView.xib.

Save both of the NIB files and return to Xcode.

Creating the second tab

We also need to create a custom view controller for the second tab view. Go back into Xcode and right-click or Ctrl-click on the Classes group and select Add→New File. When the File window opens select a UIViewController subclass from the Cocoa Touch Class panel, but unlike earlier examples in this book, uncheck the “With XIB for user interface” box (we already have a SecondView.xib file in the project). Click Next. When asked, name the new view controller “SecondViewController.m”.

Go back to MainWindow.xib in Interface Builder and click on the “Second View Controller (Second)” entry. In the Identity Inspector (⌘-4) change the Class identity from UIViewController to SecondViewController. You don’t need to connect the view outlet as you did for theFirstView.xib file because Xcode created the second tab bar item with its outlets connected correctly by default.

Wrapping up the refactoring

After doing this refactoring, you should end up with a MainWindow.xib file that looks a lot like that shown in Figure 6-9. Make sure you save the NIB file after finishing the refactoring.

The MainWindow.xib file after refactoring

Figure 6-9. The MainWindow.xib file after refactoring

Adding Another Tab Bar Item

Let’s add another tab bar item so that you can see how to create one from scratch. With MainWindow.xib open in Interface Builder, drag and drop a new tab bar item (not a tab bar) from the Library window (under Cocoa Touch→Windows, Views, & Bars) onto the tab bar controller in the MainWindow.xib window. Click the disclosure triangle next to the new tab bar item, and you’ll notice that it generates another view controller with an associated tab bar item. The new view controller is similar to “Second View Controller (Second)” before refactoring, with a type ofUIViewController. If you check the Attributes tab of the Inspector window, however, you’ll notice that the new controller currently has no view associated with it in Interface Builder, as shown in Figure 6-10.

Adding another tab bar item to the application

Figure 6-10. Adding another tab bar item to the application

We now need to add a view controller to manage this tab. Go back into Xcode and right-click or Ctrl-click on the Classes group and select Add→New File again. When the File window opens, select a UIViewController subclass from the Cocoa Touch Class panel; this time check the “With XIB for user interface” box, as you need Xcode to generate a NIB. When asked, name the new view controller “ThirdViewController.m”.

Note

For neatness, you may want to drag the ThirdViewController.xib file from the Classes group to the Resources group. You may also want to rename the ThirdViewController.xib file to ThirdView.xib to keep your naming conventions consistent throughout the application.

After doing this, edit MainWindow.xib in Interface Builder by changing the type of the newly created “View Controller (Item)” from UIViewController to ThirdViewController in the Identity tab of the Inspector window, and then set the NIB name to ThirdView in the Attributes Inspector (⌘-1).

You should explore some of the standard tab bar items that Apple provides; for example, expand the newly added third view controller’s disclosure triangle in the MainWindow.xib window. Next, select the tab bar item underneath it and open the Attributes Inspector (⌘-1). Try selecting something other than Custom for its identifier and see what happens.

This example assumes you use the Custom identifier, so make sure you set it back to Custom when you’re done exploring.

After selecting Custom, you should change the name of the item from “Item” to “Third”. Although currently our tab bar item doesn’t have an image, we could associate one with (each of) our tabs using the Image drop down in the Attributes tab. Just drag and drop the image you want to use into the project in the same way you added the images for the City Guide application in Chapter 5. To look like Apple’s icons, your images cannot be larger than 32×32 pixels in size and they must have a transparent background. I’ve found that PNG images between 20 and 30 pixels work well as tab bar icons.

Creating Tab Bar Icons

Creating tab-bar-based applications means you must create icons for the bar. You may be able to use the system-supplied icons, either by setting the Identifier by clicking on the UITabBarItem on the MainWindow NIB file and changing the Identity value in the Attributes tab of the Inspector window inside Interface Builder, or directly via code inside your view controller’s init: method, as shown here:

self.tabBarItem = [[UITabBarItem alloc]

initWithTabBarSystemItem:UITabBarSystemItemSearch tag:0];

However, the selection of available icons is fairly limited and you will inevitably have to resort to creating your own. Apple has this to say on tab bar icons: “The unselected and selected images displayed by the tab bar are derived from the images that you set. The alpha values in the source image are used to create the other images—opaque values are ignored.”

Effectively the alpha channel of your image will determine the shading of your icon. Tab bar icons should therefore be in PNG format, be no larger than 30×30 pixels in size, and have a transparent background. Multicolor icons are ignored as the icons themselves are an opaque mask which the iPhone will use to generate the actual tab bar icon.

Finishing Up

Finally, edit the three NIB files—FirstView.xib, SecondView.xib, and ThirdView.xib—and add a large (in 144 pt font) label saying “1”, “2”, and “3” to each respective view. This way you can confirm that the correct one is being activated. SecondView.xib will have some labels on it that were placed there when Xcode generated the project from its template; you can delete these labels.

Make sure you save all the NIB files. Then, click Build and Run to compile, deploy, and run the application in iPhone Simulator, as shown in Figure 6-11.

The tab bar application running in the simulator with SecondView selected as the active tab

Figure 6-11. The tab bar application running in the simulator with SecondView selected as the active tab

Despite the fact that we haven’t written a single line of code in this section, you should now have a working, if rather basic, tab bar application.

Although I haven’t walked you through the process of building a full-blown application, you should have begun to see the commonalities and familiar patterns emerging in this application. Our application has an application delegate along with three custom view controllers managing each view. This is a very similar arrangement to both the table view application we wrote in Chapter 5 and the utility application we wrote earlier in this chapter.

At this point, you may want to try building your own application on top of the infrastructure we have created so far. Start with something simple where changing something in one view affects the contents of another view. Don’t worry; take your time, and I’ll be here when you get back.

Modal View Controllers

So far in this chapter we’ve looked at two of Apple’s application templates. However, in this section we’re going to focus once again on an individual view controller—or rather, a way to present a view controller to the user. After table views and the UINavigationController it’s probably one of the most heavily used ways to present data: it’s the modal view controller.

You’ll have seen a modal controller in action many times when using your iPhone. A view slides in from the bottom of the screen and is usually dismissed with a Done button at the top of the screen. When dismissed, it slides back down the screen, disappearing at the bottom.

In the main controller we would generally have a button or other UI element; tapping this would trigger an event linked to the following method in the view controller, which would bring up the modal view:

-(void)openNewController:(id)sender {

OtherController *other = [[OtherController alloc] init];1

[self presentModalViewController:other animated:YES];2

[other release];3

}

1

We instantiate the view controller that manages the view we wish to display.

2

We present the view managed by the view controller. Note that presenting a view controller modally will explicitly retain it, hence the need for the release in the next line.

3

We release the view controller. Once it is dismissed, the retain count (see The alloc, retain, copy, and release Cycle in Chapter 4) will drop to zero.

In the modal view itself, we would implement a button or some other way to close the view, which would call this method in the view controller:

-(void)doneWithController:(id)sender {

[self dismissModalViewControllerAnimated:YES];

}

This dismisses the current modal view.

Modifying the City Guide Application

The best way to explain the modal view is to show it in action. For that we’re going to go back to the City Guide application we built in Chapter 5. We’re going to make some fairly extensive changes to it, so you should make a copy of the project first and work with the copy while you make your modifications. In this section, I’ll show you how to take your code apart and put it back together again in an organized fashion. This occurs a lot when writing applications, especially for clients who have a tendency to change their mind about what they want out of the application in the first place.

Open the Finder and navigate to the location where you saved the CityGuide project; see Figure 6-12.

The CityGuide project folder in the Finder

Figure 6-12. The CityGuide project folder in the Finder

Right-click or Ctrl-click on the folder containing the project files and select Duplicate. A folder called CityGuide copy will be created containing a duplicate of our project. You should probably rename it to something more sensible. I suggest CityGuide2. Now open the new version of the project in Xcode and select Project→Rename from the Xcode menu bar. Enter CityGuide2 when prompted and click on the Rename button to rename the project.

In Chapter 5, we built an application that lets users both add and delete city entries in our table view. Adding the functionality to delete table view cells was fairly simple; the complicated part was adding the ability to add cities. So, let’s take a step back and look at another way to implement that functionality.

First we’re going to go into the RootController implementation and back out of the changes that allowed users to edit the table view. We’re going to replace the Edit button and the associated implementation with an Add button, reusing the AddCityController code and associated view, but presenting the Add City view modally instead of using the navigation controller.

You may wonder about deleting lots of perfectly good code, but refactoring functionality like this is a fairly common task when you change your mind about how you want to present information to the user, or if the requirements driving the project change. This is good practice for you.

Note

If you want to do a global find (and replace) over the entire project for a word or phrase you can do so from the Edit menu. Selecting Edit→Find→Find in Project will bring up the Project Find window.

To remove functionality like this, first you need to figure out what needs to be removed. If you don’t know the author of the original application this can sometimes be difficult. Do a project-wide search for “editing”, as shown in Figure 6-13. If you do that you’ll see that the only mention of “editing” is in the RootController.m file. The changes we’ll need to make are actually fairly tightly constrained inside a single class. We’ll have to make some minor changes elsewhere in the project. Limiting the scope of necessary changes when refactoring code in this way is one of the main benefits of writing code in an object-oriented manner.

The results of a global find across the CityGuide2 project for “editing”

Figure 6-13. The results of a global find across the CityGuide2 project for “editing”

Open the RootController.m file in Xcode. Begin the refactoring by deleting the following methods in their entirety:

§ setEditing:animated:

§ tableView:commitEditingStyle:forRowAtIndexPath:

§ tableView:editingStyleForRowAtIndexPath:

Note

Remember that the methods as they appear in the file have longer, more complicated names. For example, setEditing:animated: is (void)setE⁠diting:(BOOL)editing animated:(BOOL) animated.

Next, do the following:

1. In the viewDidLoad: method, remove the line that adds the self.editButtonItem to the navigation bar.

2. In the tableView:cellForRowAtIndexPath: method, remove the section enclosed in the if( self.editing ) { ... } conditional statement, and the else { ... } statement that adds the “Add New City...” cell. Additionally, you should remove the line that sets theeditingAccessoryType inside the conditional statement.

3. Similarly, remove the if( self.editing ) { ... } conditional statement in the tableView:numberOfRowsInSection: method.

4. Finally, in the tableView:didSelectRowAtIndexPath: method remove the && !⁠self.editing expression from the first if block. Remove the second if block (which deals with what happens if we are editing) in its entirety.

We’re done. If you do a global find in the project for “editing” you should now come up blank, and the class should appear as shown here:

#import "RootController.h"

#import "CityGuideDelegate.h"

#import "City.h"

#import "CityController.h"

#import "AddCityController.h"

@implementation RootController

@synthesize tableView;

#pragma mark UIViewController Methods

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

}

- (void)viewDidLoad {

self.title = @"City Guide";

CityGuideDelegate *delegate =

(CityGuideDelegate *)[[UIApplication sharedApplication] delegate];

cities = delegate.cities;

}

- (void)dealloc {

[tableView release];

[cities release];

[super dealloc];

}

#pragma mark UITableViewDataSource Methods

- (UITableViewCell *)tableView:(UITableView *)tv

cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

UITableViewCell *cell =

[tv dequeueReusableCellWithIdentifier:@"cell"];

if( nil == cell ) {

cell = [[[UITableViewCell alloc]

initWithFrame:CGRectZero reuseIdentifier:@"cell"] autorelease];

}

if (indexPath.row < cities.count ) {

City *thisCity = [cities objectAtIndex:indexPath.row];

cell.textLabel.text = thisCity.cityName;

cell.textLabel.textColor = [UIColor blackColor];

}

return cell;

}

- (NSInteger)tableView:(UITableView *)tv

numberOfRowsInSection:(NSInteger)section

{

NSInteger count = cities.count;

return count;

}

#pragma mark UITableViewDelegate Methods

- (void)tableView:(UITableView *)tv

didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

CityGuideDelegate *delegate =

(CityGuideDelegate *)[[UIApplication sharedApplication] delegate];

if (indexPath.row < cities.count ) {

CityController *city =

[[CityController alloc] initWithIndexPath:indexPath];

[delegate.navController pushViewController:city animated:YES];

[city release];

}

[tv deselectRowAtIndexPath:indexPath animated:YES];

}

@end

Since you’ve now made fairly extensive changes to the view controller, you should test it to see if things are still working. Click the Build and Run button on the Xcode toolbar, and if all is well you should see something very similar to Figure 6-14. Tapping on one of the city names should take you to its city page as before.

The stripped-down City Guide application, looking a lot like it did in in

Figure 6-14. The stripped-down City Guide application, looking a lot like it did in Figure 5-16 in Chapter 5

We’ve deleted a lot of code, so let’s write some more. In the viewDidLoad: method we need to replace the Edit button that we deleted with an Add button.

Let’s add a button of style UIBarButtonSystemItemAdd and set things up so that when it is clicked it will call the addCity: method in this class. Add the following code to the viewDidLoad: method:

self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self

action:@selector(addCity:)];

Since there isn’t an addCity: method right now, we need to declare it in the RootController.h interface file. Open that file, and add this line after the @interface { ...} declaration but before the @end directive:

- (void)addCity:(id)sender;

Now add the implementation to the RootController.m file:

- (void)addCity:(id)sender {

AddCityController *addCity = [[AddCityController alloc] init];

[self presentModalViewController:addCity animated:YES];

[addCity release];

}

This looks almost identical to the snippet of code I showed you at the beginning of this section, but the modal view we’re going to display is the one managed by our AddCityController class.

Now we need to make a couple of small changes to our AddCityController class. Open the AddCityController.h interface file in Xcode and declare the saveCity: method as an IBAction. Add this line after the @interface { ... } statement but before the @end directive:

- (IBAction)saveCity:(id)sender;

Open the implementation file (AddCityController.m), and remove the last line (where we pop the view controller off the navigation controller) and replace it with a line dismissing the modal view controller. You’ll also change the return value of the saveCity: method from void toIBAction here just as you did in the interface file:

- (IBAction)saveCity:(id)sender {

CityGuideDelegate *delegate =

(CityGuideDelegate *)[[UIApplication sharedApplication] delegate];

NSMutableArray *cities = delegate.cities;

UITextField *nameEntry = (UITextField *)[nameCell viewWithTag:777];

UITextView *descriptionEntry =

(UITextView *)[descriptionCell viewWithTag:777];

if ( nameEntry.text.length > 0 ) {

City *newCity = [[City alloc] init];

newCity.cityName = nameEntry.text;

newCity.cityDescription = descriptionEntry.text;

[cities addObject:newCity];

RootController *viewController = delegate.viewController;

[viewController.tableView reloadData];

}

[self dismissModalViewControllerAnimated:YES];

}

We’re pretty much there at this point; however, before we finish with our changes here we also need to go up to the viewDidLoad: method and delete the lines where we add the Save button to the view (it’s a single statement beginning withself.navigationI⁠tem.rightBarButtonI⁠tem that spans multiple lines).

Make sure you save the changes you made to the AddCityController class, and open the AddCityController.xib file inside Interface Builder.

First, drag and drop into the view a navigation bar (UINavigationBar) from the Library window (select Cocoa Touch→Windows, Views & Bars). Position it at the top of the view, and resize the table view so that it fits in the remaining space. While you’re there, change the title of the navigation bar from “title” to “Add New City”.

Next, drag and drop a bar button item (UIBarButtonItem) onto the navigation bar and position it to the left of the title. In the Attributes Inspector (⌘-1) change the Identifier from Custom to Done. You’ll see that this changes both the text and the style of the button.

Finally, click on File’s Owner in the AddCityController.xib window and switch to the Connections Inspector (⌘-2). Connect the saveCity: received action to the Done button, as I’ve done in Figure 6-15. Save your changes to the NIB file, as we’ve now finished refactoring our City Guide application.

Click Build and Run on the Xcode toolbar to compile and start the application in iPhone Simulator. When the application starts you should see something like Figure 6-16. Clicking the Add button in the navigation bar should bring up our “Add City” view; when it does, enter some information and click Done. You should see your test city appear in the main table view.

Well done. We’ve just taken the City Guide application apart, put it back together again, and made it work slightly differently. But what if you disliked the way we implemented the ability to add cities in the first version of the application, preferring this approach, but you still want to retain the ability to delete cities? You could still implement things so that a left-to-right swipe brought up the Delete button for the row; for instance, Apple’s Mail application that ships with the iPhone and iPod touch takes this approach. Just adding the following method back intoRootController.m will reimplement this functionality:

- (void) tableView:(UITableView *)tv

commitEditingStyle:(UITableViewCellEditingStyle) editing

forRowAtIndexPath:(NSIndexPath *)indexPath {

if( editing == UITableViewCellEditingStyleDelete ) {

[cities removeObjectAtIndex:indexPath.row];

[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]

withRowAnimation:UITableViewRowAnimationLeft];

}

}

Connecting the SaveCity: received action to the Done button in our newly modified AddCityController.xib file

Figure 6-15. Connecting the SaveCity: received action to the Done button in our newly modified AddCityController.xib file

The newly rewritten City Guide application, with our Add button on the right of the navigation bar

Figure 6-16. The newly rewritten City Guide application, with our Add button on the right of the navigation bar

The Image Picker View Controller

As I promised in Chapter 5, I’m going to talk about the image picker view controller. This view controller manages Apple-supplied interfaces for choosing images and movies, and on supported devices it takes new images or movies with the camera. As this class handles all of the required interaction with the user, it is very simple to use. All you need to do is tell it to start, and then dismiss it after the user selects an image or movie.

Adding the Image Picker to the City Guide Application

In this section, we’ll continue to build on our City Guide application. Either of the two versions of the application we now have will do, as all of the changes we’re going to make will be confined to the AddCityController class. In the preceding section, we made only relatively minor changes in this class that won’t affect our additions here.

However, if you want to follow along, I’m going to return to our original version and work on that. As we did in the preceding section, you should work on a copy of the project, so right-click or Ctrl-click on the folder containing the project files and select Duplicate. A folder calledCityGuide copy will be created containing a duplicate of our project. You should probably rename the folder to something more sensible. I suggest CityGuide3, and renaming the project by selecting Project→Rename from the Xcode menu bar.

The first thing we need to do is build an interface to allow the user to trigger the image picker. If you remember from Chapter 5, our “Add City” view was built out of two custom table view cells. The easiest way to add this ability is to add another table view cell.

Open the AddCityController.xib file in Interface Builder. Drag and drop a table view cell (UITableViewCell) from the Library window into the AddCityController.xib window. We need to resize this cell so that it can hold a small thumbnail of our selected image, so go to the Size Inspector (⌘-3) and change its height from the default 44 pixels to H = 83 pixels. At this point, we also need to resize the super-size table view cell for entering the description to account for this new cell. So, click on the description cell and go to the Size tab of the Inspector window and change the height from H = 362 to H = 279 pixels.

Go back to the new cell and grab a label (UILabel) from the Library window and drop it onto the Table View Cell window (if the window is not open already, double-click on the new cell in the AddCityController.xib window to open it). In the Attributes Inspector (⌘-1) change the label’s text to “Add a picture:” and then switch to the Size tab and position the label at X = 10 and Y = 28 with W = 126 and H = 21 pixels.

Next, grab an image view (UIImageView) from the Library window and drop it onto the cell, then position it at X = 186 and Y = 7 and resize it to be W = 83 and H = 63 using the Size tab of the Inspector window. In the Attributes tab, set the Tag attribute to 777 (this lets us easily refer to this subview from our code) and set the view mode to Aspect Fill.

Finally, drop a round rect button (UIButton) onto the cell, and in the Attributes tab change its type from Rounded Rect to Add Contact. The button should now appear as a blue circle enclosing a plus sign. Position it to the right of the UIImageView, at X = 274 and Y = 25.

After doing this, you should have something that looks a lot like Figure 6-17. Set the cell selection type to None in the Attributes tab, make sure you’ve saved your changes to the NIB, and then open the AddCityController.h and AddCityController.m files in Xcode.

The Add Picture table view cell in Interface Builder with the UIImageView tagged as view 777 so that we can access its subview from code more easily

Figure 6-17. The Add Picture table view cell in Interface Builder with the UIImageView tagged as view 777 so that we can access its subview from code more easily

In the AddCityController.h interface file, the first thing we need to do is add an IBOutlet to allow us to connect our code to the new table view cell inside Interface Builder. We must also add an instance variable of type UIImage called cityPicture, which we’ll use to hold the image passed back to us from the image picker, along with an addPicture: method that we’ll connect to the UIButton in the cell, allowing us to start the image picker. Add the lines shown in bold to the file:

#import <UIKit/UIKit.h>

@interface AddCityController : UIViewController

<UITableViewDataSource, UITableViewDelegate> {

IBOutlet UITableView *tableView;

IBOutlet UITableViewCell *nameCell;

IBOutlet UITableViewCell *pictureCell;

IBOutlet UITableViewCell *descriptionCell;

UIImage *cityPicture;

}

- (void)saveCity:(id)sender;

- (IBAction)addPicture:(id)sender;

@end

Before implementing the code to go with this interface, we need to quickly go back into Interface Builder and make those two connections. Open the AddCityController.xib file and click on File’s Owner, then use the Connections Inspector (⌘-2) to connect the pictureCell outlet to your new UITableViewCell. Next, click on the addPicture: received action and connect it to the UIButton in your table view cell; see Figure 6-18. When you release the mouse button you’ll be presented with a pop-up menu of possible events the button can generate (just like the Hello World example back in Chapter 3). We want just a simple button click, so select the Touch Up Inside event.

Connecting the addCity: received action to the UIButton in our new UITableViewCell to allow it to trigger the image picker

Figure 6-18. Connecting the addCity: received action to the UIButton in our new UITableViewCell to allow it to trigger the image picker

We now need to save this file, and then go back into Xcode to finish our implementation. In the AddCityController.m implementation file, first we have to provide a default image for the UIImage in the cell (otherwise, it will appear blank). We can do this inside the viewDidLoad:method by adding this line (you’ll need an image called QuestionMark.jpg for this to work; see Capturing the City Data in Chapter 5 for information on using this image in your project):

cityPicture = [UIImage imageNamed:@"QuestionMark.jpg"];

We also have to make some changes to the table view delegate and data source methods (in the AddCityController.m implementation file) to take account of the new cell. First we need to change the number of rows returned by the tableView:numberOfRowsInSection: method from two to three. Make the change shown in bold:

- (NSInteger)tableView:(UITableView *)tv

numberOfRowsInSection:(NSInteger)section

{

return 3;

}

Now we need to modify the tableView:cellForRowAtIndexPath: method to return the extra cell in the correct position in our table view. Make the changes shown in bold:

- (UITableViewCell *)tableView:(UITableView *)tv

cellForRowAtIndexPath:(NSIndexPath *)indexPath {

UITableViewCell *cell = nil;

if( indexPath.row == 0 ) {1

cell = nameCell;

} else if ( indexPath.row == 1 ) {2

UIImageView *pictureView = (UIImageView *)[pictureCell viewWithTag:777];

pictureView.image = cityPicture;

cell = pictureCell;

} else {3

cell = descriptionCell;

}

return cell;

}

1

In the first row of the table view, we return a nameCell, configured to allow the user to enter the city name.

2

In the second row of the table view, we return the cell we just added. We first populate the UIImageView with the image held by the cityPicture variable that we initialized in the viewDidLoad: method earlier.

3

Finally, we return the table view cell that we set up to allow the user to enter a description for the city.

We also need to change the tableView:heightForRowAtIndexPath: method to take account of the new cell. Make the changes shown in bold:

- (CGFloat)tableView:(UITableView *)tv

heightForRowAtIndexPath:(NSIndexPath *)indexPath

{

CGFloat height;

if( indexPath.row == 0 ) {

height = 44;

} else if( indexPath.row == 1 ) {

height = 83;

} else {

height = 279;

}

return height;

}

We also need to remember to release the pictureCell variables in the dealloc: method. We don’t have to release the cityPicture variable because it will be part of the autorelease pool. Add the following to the dealloc: method:

[pictureCell release];

Finally, we need to add a placeholder implementation (after the instance methods pragma mark) for our addPicture: method, which we’ll fill in later:

- (IBAction)addPicture:(id)sender {

NSLog(@"addPicture: called.");

}

We’re done, at least for now. Click Build and Run in the Xcode toolbar to compile and run the application in iPhone Simulator. Once the application has started, tap the Edit button in the navigation bar and click Add New City (if you chose to modify the second version of the guide, click the Add button). Figure 6-19 shows the new view.

The New City view with our new UITableViewCell

Figure 6-19. The New City view with our new UITableViewCell

Now we have an interface to trigger the image picker for us, so let’s implement the code to do that. First we need to add a UIImagePickerController variable to the AddCityController.h interface file, along with a UIImage variable to hold the image returned by the image picker. We also need to declare the class to be a delegate. Make the changes shown in bold:

@interface AddCityController : UIViewController

<UITableViewDataSource, UITableViewDelegate,

UIImagePickerControllerDelegate, UINavigationControllerDelegate> {1

IBOutlet UITableView *tableView;

IBOutlet UITableViewCell *nameCell;

IBOutlet UITableViewCell *pictureCell;

IBOutlet UITableViewCell *descriptionCell;

UIImage *cityPicture;

UIImagePickerController *pickerController;

}

- (void)saveCity:(id)sender;

- (IBAction)addPicture:(id)sender;

@end

1

We need to declare the class as both a UIImagePickerControllerDelegate and a UINavigationControllerDelegate. Both declarations are necessary for the class to interact with the UIImagePickerController.

In the AddCityController.m implementation file, we need to modify the viewDidLoad: method to initialize our UIImagePickerController. Make the changes shown in bold:

- (void)viewDidLoad {

self.title = @"New City";

self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemSave

target:self action:@selector(saveCity:)];

cityPicture = [UIImage imageNamed:@"QuestionMark.jpg"];

pickerController = [[UIImagePickerController alloc] init];1

pickerController.allowsImageEditing = NO;2

pickerController.delegate = self;3

pickerController.sourceType =

UIImagePickerControllerSourceTypeSavedPhotosAlbum;4

}

1

We allocate and initialize the UIImagePickerController (this means we’re responsible for it and we must release it inside our dealloc: method).

2

When using the image picker, the user may be allowed to edit the selected image before it is passed to our code. This disables that option here.

3

We set the delegate class to be this class.

4

Finally, we select the image source. There are three: UIImagePickerControllerSourceTypeCamera, UIImagePickerControllerSourceTypePhotoLibrary, and UIImagePickerControllerSourceTypeSavedPhotosAlbum. Each presents different views to the user, allowing him to take an image with the camera, pick it from the image library, or choose something from his photo album.

We also need to implement the addPicture: method, the method called when we tap the button in our interface. This method simply starts the image picker interface, presenting it as a modal view controller. Replace the placeholder addPicture: method you added to theAddCityController.m file as part of the instance methods pragma section with the following:

- (IBAction)addPicture:(id)sender {

[self presentModalViewController:pickerController animated:YES];

}

Next, we need to implement the delegate method that will tell our code the user has finished with the picker interface, the imagePickerController:didFinishPickingMediaWithInfo: method. Add the following to AddCityController.m inside theUIImagePickerController method’s pragma section:

- (void)imagePickerController:(UIImagePickerController *)picker

didFinishPickingMediaWithInfo:(NSDictionary *)info

{

[self dismissModalViewControllerAnimated:YES];1

cityPicture = [info objectForKey:@"UIImagePickerControllerOriginalImage"];2

UIImageView *pictureView = (UIImageView *)[pictureCell viewWithTag:777];3

pictureView.image = cityPicture;

[tableView reloadData];

}

1

We dismiss the image picker interface.

2

We grab the UIImage selected by the user from the NSDictionary returned by the image picker and set the cityPicture variable.

3

We grab a reference to the thumbnail UIImageView, populate it with the chosen image, and reload the table view so that the displayed image is updated.

Finally, in the saveCity: method, we need to add a line just before we add the newCity to the cities array. Add the line shown in bold:

newCity.cityPicture = nil;

newCity.cityPicture = cityPicture;

[cities addObject:newCity];

This will take our new picture and serialize it into the data model for our application.

It’s time to test our application. Make sure you’ve saved your changes and click Build and Run.

Note

If you test the application in iPhone Simulator, you’ll notice that there are no images in the Saved Photos folder. There is a way around this problem. In the simulator, tap the Safari icon and drag and drop a picture from your computer (you can drag it from the Finder or iPhoto) into the browser. You’ll notice that the URL bar displays the file path to the image. Click and hold down the cursor over the image and a dialog will appear allowing you to save the image to the Saved Photos folder.

Once the application has started, tap the Edit button in the navigation bar and go to the New City view. Tapping the blue button will open the image picker, as shown in Figure 6-20, and allow you to select an image. Once you’ve done this, the image picker will be dismissed and you’ll return to the New City interface.

The UIImagePickerController and the New City view with a new image

Figure 6-20. The UIImagePickerController and the New City view with a new image

Is everything working? Not exactly; depending on how you tested the interface you may have noticed the problem. Currently, if you enter text in the City field and then click on the “Add a picture” button before clicking on the Description field, the text in the City field will be lost when you return from the image picker. However, if you enter text in the City field and then enter text in (or just click on) the Description field, the text will still be there when you return from the image picker. Any text entered in the Description field will remain in any case.

This is actually quite a subtle bug and is a result of the different ways in which a UITextField and UITextView interact as first responders. We’re going to talk about the responder chain in Chapter 8 when we deal with data handling in more detail. However, to explain this without getting into too much detail, the first responder is the object in the application that is the current recipient of any UI events (such as a touch). The UIWindow class sends events to the registered first responder, giving it the first chance to handle the event. If it fails to do so, the event will be passed to the next object.

By default, the UITextField doesn’t commit any changes to its text until it is no longer the first responder, which is where the problem comes from. While we could change this behavior through the UITextFieldDelegate protocol, there is a simpler fix. Add the lines shown in bold to the addPicture: method:

- (IBAction)addPicture:(id)sender {

UITextField *nameEntry = (UITextField *)[nameCell viewWithTag:777];

[nameEntry resignFirstResponder];

[self presentModalViewController:pickerController animated:YES];

}

With this change, we force the UITextField to resign as first responder before we open the image picker. This means that when the image picker is dismissed, the text we entered before opening it will remain when we are done.

Save your changes, and click on the Build and Run button in the Xcode toolbar. When the application starts up, return to the New City view and confirm that this simple change fixes the bug.

We’re done with the City Guide application for a while. However, we’ll be back in Chapter 8, where I’ll fix the last remaining problem with the application and talk about data storage. (Until then, cities you add will not be saved when you exit the application, so don’t enter all your favorite cities just yet.)