13
Camera

In this chapter, you’re going to add photos to the Homepwner application. You will present a UIImagePickerController so that the user can take and save a picture of each possession. The image will then be associated with a Possession instance, stored in an image store, and viewable in the possession’s detail view. Then, when the insurance company demands proof, the user has a visual record of owning that 70" HDTV.

Figure 13.1  Homepwner with camera addition

Homepwner with camera addition

Displaying Images and UIImageView

Because we want the image to appear in the possession’s detail view, your first step is to have the ItemDetailViewController get and display an image. An easy way to display an image is to put an instance of UIImageView on the screen. Open Homepwner.xcodeproj and click ItemDetailViewController.xib in the project navigator to open the interface in the canvas area. Then drag an instance of UIImageView onto the view, as shown in Figure 13.2.

Figure 13.2  UIImageView on ItemDetailViewController’s view

UIImageView on ItemDetailViewController’s view

A UIImageView displays an image according to its contentMode property. This property determines where to position and how to resize the content within its frame. The default value for contentMode is UIViewContentModeCenter, which centers but does not resize the content to fit within the bounds of the view. If you keep the default, the large image produced by the camera will take up most of the screen. You have to change the contentMode of the image view so that it resizes the image.

Select the UIImageView and open the attributes inspector. Find the Mode attribute and change it to Aspect Fit, as shown in Figure 13.3. This will resize the image to fit within the bounds of the UIImageView.

Figure 13.3  Image view attributes

Image view attributes

Now, Option-click ItemDetailViewController.h in the project navigator to open it in the assistant editor. Control-drag from the UIImageView to the instance variable area in ItemDetailViewController.h. Name the outlet imageView and click Connect.

Because imageView is a new subview of ItemDetailViewController’s view that is instantiated when the XIB file is loaded, it needs to be released and its pointer cleared in viewDidUnload. In ItemDetailViewController.m, make the following changes.

-​ ​(​v​o​i​d​)​v​i​e​w​D​i​d​U​n​l​o​a​d​
{​
 ​ ​ ​ ​[​s​u​p​e​r​ ​v​i​e​w​D​i​d​U​n​l​o​a​d​]​;​

 ​ ​ ​ ​[​n​a​m​e​F​i​e​l​d​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​n​a​m​e​F​i​e​l​d​ ​=​ ​n​i​l​;​

 ​ ​ ​ ​[​s​e​r​i​a​l​N​u​m​b​e​r​F​i​e​l​d​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​s​e​r​i​a​l​N​u​m​b​e​r​F​i​e​l​d​ ​=​ ​n​i​l​;​

 ​ ​ ​ ​[​v​a​l​u​e​F​i​e​l​d​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​v​a​l​u​e​F​i​e​l​d​ ​=​ ​n​i​l​;​

 ​ ​ ​ ​[​d​a​t​e​L​a​b​e​l​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​d​a​t​e​L​a​b​e​l​ ​=​ ​n​i​l​;​

 ​ ​ ​ ​[​i​m​a​g​e​V​i​e​w​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​i​m​a​g​e​V​i​e​w​ ​=​ ​n​i​l​;​
}​

Also release the image view in dealloc:

-​ ​(​v​o​i​d​)​d​e​a​l​l​o​c​
{​
 ​ ​ ​ ​[​n​a​m​e​F​i​e​l​d​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​s​e​r​i​a​l​N​u​m​b​e​r​F​i​e​l​d​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​v​a​l​u​e​F​i​e​l​d​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​d​a​t​e​L​a​b​e​l​ ​r​e​l​e​a​s​e​]​;​

 ​ ​ ​ ​[​i​m​a​g​e​V​i​e​w​ ​r​e​l​e​a​s​e​]​;​

 ​ ​ ​ ​[​s​u​p​e​r​ ​d​e​a​l​l​o​c​]​;​
}​

Taking pictures and UIImagePickerController

Now you need a button to initiate the photo-taking process. It would be nice to put this button on the navigation bar, but we will need the navigation bar for another button later. Instead, we will create an instance of UIToolbar and place it at the bottom of ItemDetailViewController’s view. In ItemDetailViewController.xib, drag a UIToolbar onto the bottom of the view.

A UIToolbar works a lot like a UINavigationBar in that you can add UIBarButtonItems to it. However, where a navigation bar has two bar button items, a toolbar has an array of items. You can place as many UIBarButtonItems in a toolbar as can fit on the screen.

By default, a new instance of UIToolbar created in a XIB file comes with one UIBarButtonItem. Select this bar button item and open the attribute inspector. Change the Identifier to Camera, and the item will show a camera icon (Figure 13.4).

Figure 13.4  UIToolbar with bar button item

UIToolbar with bar button item

The camera button needs to send a message to the instance of ItemDetailViewController when it is tapped. In previous exercises, you connected action methods in two steps: declaring them in the header file and then hooking them up in the XIB file. Just like you did with outlets, you can do both steps at once by opening a source file in the assistant editor and Control-dragging from a XIB file to the file. Option-click ItemDetailViewController.h in the project navigator to open it in the assistant editor.

Select the camera button and Control-drag from the button to the method declaration area in ItemDetailViewController.h (Figure 13.5).

Figure 13.5  Creating and connecting an action method from a XIB

Creating and connecting an action method from a XIB

Let go of the mouse, and a window will appear that allows you to specify the type of connection you are creating. From the Connection pop-up menu, choose Action. Then, name this method takePicture: and click Connect (Figure 13.6).

Figure 13.6  Creating the action

Creating the action

Now the action method is declared in the header file, and the UIBarButtonItem instance in the XIB is hooked up to send this message to the ItemDetailViewController when tapped. There is also a stub for the method in ItemDetailViewController.m.

-​ ​(​I​B​A​c​t​i​o​n​)​t​a​k​e​P​i​c​t​u​r​e​:​(​i​d​)​s​e​n​d​e​r​
{​
}​

In the takePicture: method, you will instantiate a UIImagePickerController and present it on the screen. When creating an instance of UIImagePickerController, you must set its sourceType property and assign it a delegate.

The sourceType is a constant that tells the image picker where to get images. It has three possible values:

  • UIImagePickerControllerSourceTypeCamera – The user will take a new picture.
  • UIImagePickerControllerSourceTypePhotoLibrary – The user will be prompted to select an album and then a photo from that album.
  • UIImagePickerControllerSourceTypeSavedPhotosAlbum – The user picks from the most recently taken photos.

Figure 13.7 shows the results of using each constant.

Figure 13.7  UIImagePickerControllerTypes

UIImagePickerControllerTypes

The first source type, UIImagePickerControllerSourceTypeCamera, won’t work on a device that doesn’t have a camera. So before using this type, you have to check for a camera by sending the UIImagePickerController class the message isSourceTypeAvailable:. Sending this message to UIImagePickerController with one of the source type constants returns a boolean value for whether the device supports that source type.

In addition to a source type, the UIImagePickerController instance needs a delegate to handle requests from its view. When the user taps the Use Photo button on the UIImagePickerController’s interface, the delegate is sent the message imagePickerController:​didFinishPickingMediaWithInfo:. (The delegate receives another message – imagePickerControllerDidCancel: – if the process was cancelled.)

Once the UIImagePickerController has a source type and a delegate, it’s time to put its view on the screen. Unlike other UIViewController subclasses you’ve used, an instance of UIImagePickerController is presented modally. When a view controller is modal, it takes over the entire screen until it has finished its work. To present a view modally, presentModalViewController:animated: is sent to the UIViewController whose view is on the screen. The view controller to be presented is passed to it, and its view slides up from the bottom of the screen.

In ItemDetailViewController.m, implement the method takePicture: to create, configure, and present the UIImagePickerController. (Remember – there’s already a stub for this method, so locate the stub in ItemDetailViewController.m and add the following code there.)

-​ ​(​v​o​i​d​)​t​a​k​e​P​i​c​t​u​r​e​:​(​i​d​)​s​e​n​d​e​r​
{​
 ​ ​ ​ ​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​ ​*​i​m​a​g​e​P​i​c​k​e​r​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​[​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​

 ​ ​ ​ ​/​/​ ​I​f​ ​o​u​r​ ​d​e​v​i​c​e​ ​h​a​s​ ​a​ ​c​a​m​e​r​a​,​ ​w​e​ ​w​a​n​t​ ​t​o​ ​t​a​k​e​ ​a​ ​p​i​c​t​u​r​e​,​ ​o​t​h​e​r​w​i​s​e​,​ ​w​e​
 ​ ​ ​ ​/​/​ ​j​u​s​t​ ​p​i​c​k​ ​f​r​o​m​ ​p​h​o​t​o​ ​l​i​b​r​a​r​y​
 ​ ​ ​ ​i​f​ ​(​[​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​i​s​S​o​u​r​c​e​T​y​p​e​A​v​a​i​l​a​b​l​e​:​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​S​o​u​r​c​e​T​y​p​e​C​a​m​e​r​a​]​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​[​i​m​a​g​e​P​i​c​k​e​r​ ​s​e​t​S​o​u​r​c​e​T​y​p​e​:​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​S​o​u​r​c​e​T​y​p​e​C​a​m​e​r​a​]​;​
 ​ ​ ​ ​}​ ​e​l​s​e​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​[​i​m​a​g​e​P​i​c​k​e​r​ ​s​e​t​S​o​u​r​c​e​T​y​p​e​:​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​S​o​u​r​c​e​T​y​p​e​P​h​o​t​o​L​i​b​r​a​r​y​]​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​/​/​ ​T​h​i​s​ ​l​i​n​e​ ​o​f​ ​c​o​d​e​ ​w​i​l​l​ ​g​e​n​e​r​a​t​e​ ​2​ ​w​a​r​n​i​n​g​s​ ​r​i​g​h​t​ ​n​o​w​,​ ​i​g​n​o​r​e​ ​t​h​e​m​
 ​ ​ ​ ​[​i​m​a​g​e​P​i​c​k​e​r​ ​s​e​t​D​e​l​e​g​a​t​e​:​s​e​l​f​]​;​

 ​ ​ ​ ​/​/​ ​P​l​a​c​e​ ​i​m​a​g​e​ ​p​i​c​k​e​r​ ​o​n​ ​t​h​e​ ​s​c​r​e​e​n​
 ​ ​ ​ ​[​s​e​l​f​ ​p​r​e​s​e​n​t​M​o​d​a​l​V​i​e​w​C​o​n​t​r​o​l​l​e​r​:​i​m​a​g​e​P​i​c​k​e​r​ ​a​n​i​m​a​t​e​d​:​Y​E​S​]​;​

 ​ ​ ​ ​/​/​ ​T​h​e​ ​i​m​a​g​e​ ​p​i​c​k​e​r​ ​w​i​l​l​ ​b​e​ ​r​e​t​a​i​n​e​d​ ​b​y​ ​I​t​e​m​D​e​t​a​i​l​V​i​e​w​C​o​n​t​r​o​l​l​e​r​
 ​ ​ ​ ​/​/​ ​u​n​t​i​l​ ​i​t​ ​h​a​s​ ​b​e​e​n​ ​d​i​s​m​i​s​s​e​d​
 ​ ​ ​ ​[​i​m​a​g​e​P​i​c​k​e​r​ ​r​e​l​e​a​s​e​]​;​
}​

You can build and run the application now. Select a Possession to see its details and then tap the camera button on the UIToolbar. UIImagePickerController’s interface will appear on the screen (Figure 13.8), and you can take a picture (or choose an existing image if you don’t have a camera). Tapping the Use Photo button dismisses the UIImagePickerController.

Figure 13.8  UIImagePickerController preview interface

UIImagePickerController preview interface

But, oops – you don’t have a reference to the image anywhere in the code. You need to implement the delegate method imagePickerController:​didFinishPickingMediaWithInfo: in ItemDetailViewController to hold on to the selected image. But before you implement this method, let’s take care of the two warnings that appeared during the last build telling you that ItemDetailViewController does not conform to the UIImagePickerControllerDelegate or the UINavigationControllerDelegate protocol. In ItemDetailViewController.h, add the protocols to the class declaration. (Why UINavigationControllerDelegate? UIImagePickerController is a subclass of UINavigationController.)

@​i​n​t​e​r​f​a​c​e​ ​I​t​e​m​D​e​t​a​i​l​V​i​e​w​C​o​n​t​r​o​l​l​e​r​ ​:​ ​U​I​V​i​e​w​C​o​n​t​r​o​l​l​e​r​
 ​ ​ ​ ​<​U​I​N​a​v​i​g​a​t​i​o​n​C​o​n​t​r​o​l​l​e​r​D​e​l​e​g​a​t​e​,​ ​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​D​e​l​e​g​a​t​e​>​
{​

That’s better. Now we’re all up to code.

When a photo is selected, the imagePickerController:​didFinishPickingMediaWithInfo: message will be sent to the image picker’s delegate. In ItemDetailViewController.m, implement this method to put the image into the UIImageView that you created earlier.

-​ ​(​v​o​i​d​)​i​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​:​(​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​ ​*​)​p​i​c​k​e​r​
d​i​d​F​i​n​i​s​h​P​i​c​k​i​n​g​M​e​d​i​a​W​i​t​h​I​n​f​o​:​(​N​S​D​i​c​t​i​o​n​a​r​y​ ​*​)​i​n​f​o​
{​
 ​ ​ ​ ​/​/​ ​G​e​t​ ​p​i​c​k​e​d​ ​i​m​a​g​e​ ​f​r​o​m​ ​i​n​f​o​ ​d​i​c​t​i​o​n​a​r​y​
 ​ ​ ​ ​U​I​I​m​a​g​e​ ​*​i​m​a​g​e​ ​=​ ​[​i​n​f​o​ ​o​b​j​e​c​t​F​o​r​K​e​y​:​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​O​r​i​g​i​n​a​l​I​m​a​g​e​]​;​

 ​ ​ ​ ​/​/​ ​P​u​t​ ​t​h​a​t​ ​i​m​a​g​e​ ​o​n​t​o​ ​t​h​e​ ​s​c​r​e​e​n​ ​i​n​ ​o​u​r​ ​i​m​a​g​e​ ​v​i​e​w​
 ​ ​ ​ ​[​i​m​a​g​e​V​i​e​w​ ​s​e​t​I​m​a​g​e​:​i​m​a​g​e​]​;​

 ​ ​ ​ ​/​/​ ​T​a​k​e​ ​i​m​a​g​e​ ​p​i​c​k​e​r​ ​o​f​f​ ​t​h​e​ ​s​c​r​e​e​n​ ​-​
 ​ ​ ​ ​/​/​ ​y​o​u​ ​m​u​s​t​ ​c​a​l​l​ ​t​h​i​s​ ​d​i​s​m​i​s​s​ ​m​e​t​h​o​d​
 ​ ​ ​ ​[​s​e​l​f​ ​d​i​s​m​i​s​s​M​o​d​a​l​V​i​e​w​C​o​n​t​r​o​l​l​e​r​A​n​i​m​a​t​e​d​:​Y​E​S​]​;​
}​

Build and run the application again. Take a photo, and the image picker is dismissed and you are returned to the ItemDetailViewController’s view. Do you see your image? Oddly enough, you might see it or you might not. Let’s figure out what’s going on and fix the problem.

When a photo is taken, that image is loaded into memory. However, the image file is so large that it causes a low-memory warning. Recall that a low-memory warning gives the system the option of requiring view controllers to release their views if they are not currently visible. When a modal view controller is on the screen, its view is visible – and the view of the view controller that presented it is not. In our case, the low-memory warning destroys ItemDetailViewController’s view, and the imageView is no longer available when we try to set it.

To get around this problem, we must create a separate store for images. Instead of putting the image directly into the imageView, we will put it into this store. Then when the ItemDetailViewController’s view next appears on screen, we’ll have the ItemDetailViewController grab the image from the image store and put it into its own imageView. In general, this is a best practice: a view controller should re-populate its view’s subviews with data whenever it is sent the message viewWillAppear:, eliminating the possibility that a low-memory warning could wipe out its content.

ImageStore

The image store will hold all the pictures the user will take. In Chapter 15, you will have the Possession objects write out their instance variables to a file, which will then be read in when the application starts. However, as we’ve found out, images tend to be very large, so it’s a good idea to keep them separate from the other possession data. The image store will fetch and cache the images as they are needed. It will also be able to flush the cache if the device runs low on memory.

All of that nifty saving/fetching/loading stuff comes later; in this chapter, the image store is little more than a dictionary of key-value pairs in which the keys are unique strings and the values are images. Create a new NSObject subclass called ImageStore. Open ImageStore.h and create its interface:

#​i​m​p​o​r​t​ ​<​U​I​K​i​t​/​U​I​K​i​t​.​h​>​

@​i​n​t​e​r​f​a​c​e​ ​I​m​a​g​e​S​t​o​r​e​ ​:​ ​N​S​O​b​j​e​c​t​
{​
 ​ ​ ​ ​N​S​M​u​t​a​b​l​e​D​i​c​t​i​o​n​a​r​y​ ​*​d​i​c​t​i​o​n​a​r​y​;​
}​
+​ ​(​I​m​a​g​e​S​t​o​r​e​ ​*​)​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​;​

-​ ​(​v​o​i​d​)​s​e​t​I​m​a​g​e​:​(​U​I​I​m​a​g​e​ ​*​)​i​ ​f​o​r​K​e​y​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​;​
-​ ​(​U​I​I​m​a​g​e​ ​*​)​i​m​a​g​e​F​o​r​K​e​y​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​;​
-​ ​(​v​o​i​d​)​d​e​l​e​t​e​I​m​a​g​e​F​o​r​K​e​y​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​;​

@​e​n​d​

NSDictionary

Notice that the dictionary is an instance of NSMutableDictionary. A dictionary is a collection object similar to an array. However, an array is an ordered list of pointers to objects that is accessed by an index. When you have an array, you can ask it for the object at the nth index:

 ​ ​ ​ ​/​/​ ​P​u​t​ ​s​o​m​e​ ​o​b​j​e​c​t​ ​a​t​ ​t​h​e​ ​b​e​g​i​n​n​i​n​g​ ​o​f​ ​a​n​ ​a​r​r​a​y​
 ​ ​ ​ ​[​s​o​m​e​A​r​r​a​y​ ​i​n​s​e​r​t​O​b​j​e​c​t​:​s​o​m​e​O​b​j​e​c​t​ ​a​t​I​n​d​e​x​:​0​]​;​

 ​ ​ ​ ​/​/​ ​G​e​t​ ​t​h​a​t​ ​s​a​m​e​ ​o​b​j​e​c​t​ ​o​u​t​
 ​ ​ ​ ​s​o​m​e​O​b​j​e​c​t​ ​=​ ​[​s​o​m​e​A​r​r​a​y​ ​o​b​j​e​c​t​A​t​I​n​d​e​x​:​0​]​;​

A dictionary’s objects are not ordered within the collection. So instead of accessing entries with an index, you use a key. The key is usually an instance of NSString.

 ​ ​ ​ ​/​/​ ​A​d​d​ ​s​o​m​e​ ​o​b​j​e​c​t​ ​t​o​ ​a​ ​d​i​c​t​i​o​n​a​r​y​ ​f​o​r​ ​t​h​e​ ​k​e​y​ ​"​M​y​K​e​y​"​
 ​ ​ ​ ​[​s​o​m​e​D​i​c​t​i​o​n​a​r​y​ ​s​e​t​O​b​j​e​c​t​:​s​o​m​e​O​b​j​e​c​t​ ​f​o​r​K​e​y​:​@​"​M​y​K​e​y​"​]​;​

 ​ ​ ​ ​/​/​ ​G​e​t​ ​t​h​a​t​ ​s​a​m​e​ ​o​b​j​e​c​t​ ​o​u​t​
 ​ ​ ​ ​s​o​m​e​O​b​j​e​c​t​ ​=​ ​[​s​o​m​e​D​i​c​t​i​o​n​a​r​y​ ​o​b​j​e​c​t​F​o​r​K​e​y​:​@​"​M​y​K​e​y​"​]​;​

An NSDictionary is useful when you want to access entries within a collection by name. In other development environments, this is called a hash map or hash table (Figure 13.9).

Figure 13.9  NSDictionary diagram

NSDictionary diagram

There can only be one object for each key. If you add an object to a dictionary with a key that matches the key of an object already present in the dictionary, the earlier object is removed. If you need to store multiple objects under one key, you can put them in an array and add the array to the dictionary.

Finally, note that a dictionary’s memory management is like that of an array. Whenever you add an object to a dictionary, the dictionary retains it, and whenever you remove an object from a dictionary, the dictionary releases it.

Like the PossessionStore, the ImageStore needs to be a singleton. In ImageStore.m, write the following code to ensure ImageStore’s singleton status.

s​t​a​t​i​c​ ​I​m​a​g​e​S​t​o​r​e​ ​*​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​ ​=​ ​n​i​l​;​

@​i​m​p​l​e​m​e​n​t​a​t​i​o​n​ ​I​m​a​g​e​S​t​o​r​e​

+​ ​(​i​d​)​a​l​l​o​c​W​i​t​h​Z​o​n​e​:​(​N​S​Z​o​n​e​ ​*​)​z​o​n​e​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​[​s​e​l​f​ ​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​]​;​
}​

+​ ​(​I​m​a​g​e​S​t​o​r​e​ ​*​)​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​
{​
 ​ ​ ​ ​i​f​ ​(​!​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​t​h​e​ ​s​i​n​g​l​e​t​o​n​
 ​ ​ ​ ​ ​ ​ ​ ​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​ ​=​ ​[​[​s​u​p​e​r​ ​a​l​l​o​c​W​i​t​h​Z​o​n​e​:​N​U​L​L​]​ ​i​n​i​t​]​;​
 ​ ​ ​ ​}​
 ​ ​ ​ ​r​e​t​u​r​n​ ​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​;​
}​

-​ ​(​i​d​)​i​n​i​t​
{​
 ​ ​ ​ ​i​f​ ​(​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​r​e​t​u​r​n​ ​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​s​e​l​f​ ​=​ ​[​s​u​p​e​r​ ​i​n​i​t​]​;​
 ​ ​ ​ ​i​f​ ​(​s​e​l​f​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​d​i​c​t​i​o​n​a​r​y​ ​=​ ​[​[​N​S​M​u​t​a​b​l​e​D​i​c​t​i​o​n​a​r​y​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​l​f​;​
}​

-​ ​(​v​o​i​d​)​r​e​l​e​a​s​e​
{​
 ​ ​ ​ ​/​/​ ​n​o​ ​o​p​
}​

-​ ​(​i​d​)​r​e​t​a​i​n​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​l​f​;​
}​

-​ ​(​N​S​U​I​n​t​e​g​e​r​)​r​e​t​a​i​n​C​o​u​n​t​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​N​S​U​I​n​t​e​g​e​r​M​a​x​;​
}​

Then, implement the three methods declared in the header file.

-​ ​(​v​o​i​d​)​s​e​t​I​m​a​g​e​:​(​U​I​I​m​a​g​e​ ​*​)​i​ ​f​o​r​K​e​y​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​
{​
 ​ ​ ​ ​[​d​i​c​t​i​o​n​a​r​y​ ​s​e​t​O​b​j​e​c​t​:​i​ ​f​o​r​K​e​y​:​s​]​;​
}​

-​ ​(​U​I​I​m​a​g​e​ ​*​)​i​m​a​g​e​F​o​r​K​e​y​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​[​d​i​c​t​i​o​n​a​r​y​ ​o​b​j​e​c​t​F​o​r​K​e​y​:​s​]​;​
}​

-​ ​(​v​o​i​d​)​d​e​l​e​t​e​I​m​a​g​e​F​o​r​K​e​y​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​
{​
 ​ ​ ​ ​i​f​(​!​s​)​
 ​ ​ ​ ​ ​ ​ ​ ​r​e​t​u​r​n​;​
 ​ ​ ​ ​[​d​i​c​t​i​o​n​a​r​y​ ​r​e​m​o​v​e​O​b​j​e​c​t​F​o​r​K​e​y​:​s​]​;​
}​

Note that there is no dealloc method because once created, the store will exist for the life of the application.

Creating and using keys

When an image is added to the store, it will be put into a dictionary under a unique key, and the associated Possession object will be given that key. When the ItemDetailViewController wants an image from the store, it will ask its possession for the key and search the dictionary for the image. Add an instance variable to Possession.h to store the key.

 ​ ​ ​ ​N​S​D​a​t​e​ ​*​d​a​t​e​C​r​e​a​t​e​d​;​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​i​m​a​g​e​K​e​y​;​
}​
@​p​r​o​p​e​r​t​y​ ​(​n​o​n​a​t​o​m​i​c​,​ ​c​o​p​y​)​ ​N​S​S​t​r​i​n​g​ ​*​i​m​a​g​e​K​e​y​;​

Synthesize this new property in the implementation file.

@​i​m​p​l​e​m​e​n​t​a​t​i​o​n​ ​P​o​s​s​e​s​s​i​o​n​
@​s​y​n​t​h​e​s​i​z​e​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​,​ ​s​e​r​i​a​l​N​u​m​b​e​r​,​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​,​ ​d​a​t​e​C​r​e​a​t​e​d​;​
@​s​y​n​t​h​e​s​i​z​e​ ​i​m​a​g​e​K​e​y​;​

You also need to release this object when a Possession is deallocated. Add this code to the dealloc method in Possession.m.

-​ ​(​v​o​i​d​)​d​e​a​l​l​o​c​
{​
 ​ ​ ​ ​[​p​o​s​s​e​s​s​i​o​n​N​a​m​e​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​s​e​r​i​a​l​N​u​m​b​e​r​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​d​a​t​e​C​r​e​a​t​e​d​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​i​m​a​g​e​K​e​y​ ​r​e​l​e​a​s​e​]​;​

 ​ ​ ​ ​[​s​u​p​e​r​ ​d​e​a​l​l​o​c​]​;​
}​

The image keys need to be unique in order for your dictionary to work. While there are many ways to hack together a unique string, we’re going to use the Cocoa Touch mechanism for creating universally unique identifiers (UUIDs), also known as globally unique identifiers (GUIDs). Objects of type CFUUIDRef represent a UUID and are generated using the time, a counter, and a hardware identifier, which is usually the MAC address of the ethernet card.

However, CFUUIDRef is not an Objective-C object; it is a C structure and part of the Core Foundation API. Core Foundation is a C API that is included in template projects and contains the building blocks for applications, such as strings, arrays, and dictionaries. Core Foundation classes are prefixed with CF and suffixed with Ref. Other examples include CFArrayRef and CFStringRef.

Like Objective-C objects, Core Foundation structures have a retain count mechanism.

Many objects in Core Foundation have an Objective-C counterpart; for example, NSString * is the Objective-C counterpart of CFStringRef. However, CFUUIDRef does not have an Objective-C counterpart and, in fact, knows nothing at all about Objective-C. Thus, when it produces a UUID as a string, that string cannot be an NSString – it must be a CFStringRef.

Recall that your instance variable for the image key is of type NSString *. Do you have to change it to CFStringRef in order to work with CFUUIDRef? Nope. Many Core Foundation objects can simply be typecast as their Objective-C counterparts. Here’s an example:

 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​n​ ​i​n​s​t​a​n​c​e​ ​o​f​ ​a​ ​C​F​S​t​r​i​n​g​R​e​f​
 ​ ​ ​ ​C​F​S​t​r​i​n​g​R​e​f​ ​s​o​m​e​S​t​r​i​n​g​ ​=​ ​C​F​S​T​R​(​"​S​t​r​i​n​g​"​)​;​
 ​ ​ ​ ​/​/​ ​T​u​r​n​ ​i​t​ ​i​n​ ​t​o​ ​a​n​ ​N​S​S​t​r​i​n​g​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​c​o​o​l​e​r​S​t​r​i​n​g​ ​=​ ​(​N​S​S​t​r​i​n​g​ ​*​)​s​o​m​e​S​t​r​i​n​g​;​

We call this toll-free bridging. (And it works because the structures in memory are equivalent. How smart is that?)

At the top of ItemDetailViewController.m, import the header for ImageStore.

#​i​m​p​o​r​t​ ​"​I​m​a​g​e​S​t​o​r​e​.​h​"​

@​i​m​p​l​e​m​e​n​t​a​t​i​o​n​ ​I​t​e​m​D​e​t​a​i​l​V​i​e​w​C​o​n​t​r​o​l​l​e​r​

Now, in ItemDetailViewController.m, make changes to imagePickerController:​didFinishPickingMediaWithInfo: to create and use a key for a possession image.

-​ ​(​v​o​i​d​)​i​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​:​(​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​ ​*​)​p​i​c​k​e​r​
 ​ ​ ​ ​ ​ ​ ​ ​d​i​d​F​i​n​i​s​h​P​i​c​k​i​n​g​M​e​d​i​a​W​i​t​h​I​n​f​o​:​(​N​S​D​i​c​t​i​o​n​a​r​y​ ​*​)​i​n​f​o​
{​

 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​o​l​d​K​e​y​ ​=​ ​[​p​o​s​s​e​s​s​i​o​n​ ​i​m​a​g​e​K​e​y​]​;​

 ​ ​ ​ ​/​/​ ​D​i​d​ ​t​h​e​ ​p​o​s​s​e​s​s​i​o​n​ ​a​l​r​e​a​d​y​ ​h​a​v​e​ ​a​n​ ​i​m​a​g​e​?​
 ​ ​ ​ ​i​f​ ​(​o​l​d​K​e​y​)​ ​{​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​D​e​l​e​t​e​ ​t​h​e​ ​o​l​d​ ​i​m​a​g​e​
 ​ ​ ​ ​ ​ ​ ​ ​[​[​I​m​a​g​e​S​t​o​r​e​ ​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​]​ ​d​e​l​e​t​e​I​m​a​g​e​F​o​r​K​e​y​:​o​l​d​K​e​y​]​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​U​I​I​m​a​g​e​ ​*​i​m​a​g​e​ ​=​ ​[​i​n​f​o​ ​o​b​j​e​c​t​F​o​r​K​e​y​:​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​O​r​i​g​i​n​a​l​I​m​a​g​e​]​;​

 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​ ​C​F​U​U​I​D​ ​o​b​j​e​c​t​ ​-​ ​i​t​ ​k​n​o​w​s​ ​h​o​w​ ​t​o​ ​c​r​e​a​t​e​ ​u​n​i​q​u​e​ ​i​d​e​n​t​i​f​i​e​r​ ​s​t​r​i​n​g​s​
 ​ ​ ​ ​C​F​U​U​I​D​R​e​f​ ​n​e​w​U​n​i​q​u​e​I​D​ ​=​ ​C​F​U​U​I​D​C​r​e​a​t​e​ ​(​k​C​F​A​l​l​o​c​a​t​o​r​D​e​f​a​u​l​t​)​;​

 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​ ​s​t​r​i​n​g​ ​f​r​o​m​ ​u​n​i​q​u​e​ ​i​d​e​n​t​i​f​i​e​r​
 ​ ​ ​ ​C​F​S​t​r​i​n​g​R​e​f​ ​n​e​w​U​n​i​q​u​e​I​D​S​t​r​i​n​g​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​C​F​U​U​I​D​C​r​e​a​t​e​S​t​r​i​n​g​ ​(​k​C​F​A​l​l​o​c​a​t​o​r​D​e​f​a​u​l​t​,​ ​n​e​w​U​n​i​q​u​e​I​D​)​;​

 ​ ​ ​ ​/​/​ ​U​s​e​ ​t​h​a​t​ ​u​n​i​q​u​e​ ​I​D​ ​t​o​ ​s​e​t​ ​o​u​r​ ​p​o​s​s​e​s​s​i​o​n​s​ ​i​m​a​g​e​K​e​y​
 ​ ​ ​ ​[​p​o​s​s​e​s​s​i​o​n​ ​s​e​t​I​m​a​g​e​K​e​y​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​e​w​U​n​i​q​u​e​I​D​S​t​r​i​n​g​]​;​

 ​ ​ ​ ​/​/​ ​W​e​ ​u​s​e​d​ ​"​C​r​e​a​t​e​"​ ​i​n​ ​t​h​e​ ​f​u​n​c​t​i​o​n​s​ ​t​o​ ​m​a​k​e​ ​o​b​j​e​c​t​s​,​ ​w​e​ ​n​e​e​d​ ​t​o​ ​r​e​l​e​a​s​e​ ​t​h​e​m​
 ​ ​ ​ ​C​F​R​e​l​e​a​s​e​(​n​e​w​U​n​i​q​u​e​I​D​S​t​r​i​n​g​)​;​
 ​ ​ ​ ​C​F​R​e​l​e​a​s​e​(​n​e​w​U​n​i​q​u​e​I​D​)​;​

 ​ ​ ​ ​/​/​ ​S​t​o​r​e​ ​i​m​a​g​e​ ​i​n​ ​t​h​e​ ​I​m​a​g​e​S​t​o​r​e​ ​w​i​t​h​ ​t​h​i​s​ ​k​e​y​
 ​ ​ ​ ​[​[​I​m​a​g​e​S​t​o​r​e​ ​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​]​ ​s​e​t​I​m​a​g​e​:​i​m​a​g​e​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​o​r​K​e​y​:​[​p​o​s​s​e​s​s​i​o​n​ ​i​m​a​g​e​K​e​y​]​]​;​

 ​ ​ ​ ​/​/​ ​P​u​t​ ​t​h​a​t​ ​i​m​a​g​e​ ​o​n​t​o​ ​t​h​e​ ​s​c​r​e​e​n​ ​i​n​ ​o​u​r​ ​i​m​a​g​e​ ​v​i​e​w​
 ​ ​ ​ ​[​i​m​a​g​e​V​i​e​w​ ​s​e​t​I​m​a​g​e​:​i​m​a​g​e​]​;​

 ​ ​ ​ ​/​/​ ​T​a​k​e​ ​i​m​a​g​e​ ​p​i​c​k​e​r​ ​o​f​f​ ​t​h​e​ ​s​c​r​e​e​n​
 ​ ​ ​ ​[​s​e​l​f​ ​d​i​s​m​i​s​s​M​o​d​a​l​V​i​e​w​C​o​n​t​r​o​l​l​e​r​A​n​i​m​a​t​e​d​:​Y​E​S​]​;​
}​

In this method, we call the C functions CFUUIDCreate and CFUUIDCreateString. When the name of a C function contains the word Create, you are responsible for releasing its memory just as if you had sent the message alloc to a class. So you released these Core Foundation objects by calling the function CFRelease with the object as a parameter.

Figure 13.10  Cache

Cache

Now, when ItemDetailViewController’s view appears on the screen, it should grab an image from the ImageStore using the imageKey of the Possession. Then, it should place the image in the UIImageView. Add the following code to viewWillAppear: in ItemDetailViewController.m.

-​ ​(​v​o​i​d​)​v​i​e​w​W​i​l​l​A​p​p​e​a​r​:​(​B​O​O​L​)​a​n​i​m​a​t​e​d​
{​
 ​ ​ ​ ​[​s​u​p​e​r​ ​v​i​e​w​W​i​l​l​A​p​p​e​a​r​:​a​n​i​m​a​t​e​d​]​;​

 ​ ​ ​ ​[​n​a​m​e​F​i​e​l​d​ ​s​e​t​T​e​x​t​:​[​p​o​s​s​e​s​s​i​o​n​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​]​]​;​
 ​ ​ ​ ​[​s​e​r​i​a​l​N​u​m​b​e​r​F​i​e​l​d​ ​s​e​t​T​e​x​t​:​[​p​o​s​s​e​s​s​i​o​n​ ​s​e​r​i​a​l​N​u​m​b​e​r​]​]​;​
 ​ ​ ​ ​[​v​a​l​u​e​F​i​e​l​d​ ​s​e​t​T​e​x​t​:​[​N​S​S​t​r​i​n​g​ ​s​t​r​i​n​g​W​i​t​h​F​o​r​m​a​t​:​@​"​%​d​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​p​o​s​s​e​s​s​i​o​n​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​]​]​]​;​

 ​ ​ ​ ​N​S​D​a​t​e​F​o​r​m​a​t​t​e​r​ ​*​d​a​t​e​F​o​r​m​a​t​t​e​r​ ​=​ ​[​[​[​N​S​D​a​t​e​F​o​r​m​a​t​t​e​r​ ​a​l​l​o​c​]​ ​i​n​i​t​]​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​a​u​t​o​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​d​a​t​e​F​o​r​m​a​t​t​e​r​ ​s​e​t​D​a​t​e​S​t​y​l​e​:​N​S​D​a​t​e​F​o​r​m​a​t​t​e​r​M​e​d​i​u​m​S​t​y​l​e​]​;​
 ​ ​ ​ ​[​d​a​t​e​F​o​r​m​a​t​t​e​r​ ​s​e​t​T​i​m​e​S​t​y​l​e​:​N​S​D​a​t​e​F​o​r​m​a​t​t​e​r​N​o​S​t​y​l​e​]​;​

 ​ ​ ​ ​[​d​a​t​e​L​a​b​e​l​ ​s​e​t​T​e​x​t​:​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​d​a​t​e​F​o​r​m​a​t​t​e​r​ ​s​t​r​i​n​g​F​r​o​m​D​a​t​e​:​[​p​o​s​s​e​s​s​i​o​n​ ​d​a​t​e​C​r​e​a​t​e​d​]​]​]​;​

 ​ ​ ​ ​[​[​s​e​l​f​ ​n​a​v​i​g​a​t​i​o​n​I​t​e​m​]​ ​s​e​t​T​i​t​l​e​:​[​p​o​s​s​e​s​s​i​o​n​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​]​]​;​

 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​i​m​a​g​e​K​e​y​ ​=​ ​[​p​o​s​s​e​s​s​i​o​n​ ​i​m​a​g​e​K​e​y​]​;​

 ​ ​ ​ ​i​f​ ​(​i​m​a​g​e​K​e​y​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​G​e​t​ ​i​m​a​g​e​ ​f​o​r​ ​i​m​a​g​e​ ​k​e​y​ ​f​r​o​m​ ​i​m​a​g​e​ ​s​t​o​r​e​
 ​ ​ ​ ​ ​ ​ ​ ​U​I​I​m​a​g​e​ ​*​i​m​a​g​e​T​o​D​i​s​p​l​a​y​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​[​I​m​a​g​e​S​t​o​r​e​ ​d​e​f​a​u​l​t​I​m​a​g​e​S​t​o​r​e​]​ ​i​m​a​g​e​F​o​r​K​e​y​:​i​m​a​g​e​K​e​y​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​U​s​e​ ​t​h​a​t​ ​i​m​a​g​e​ ​t​o​ ​p​u​t​ ​o​n​ ​t​h​e​ ​s​c​r​e​e​n​ ​i​n​ ​i​m​a​g​e​V​i​e​w​
 ​ ​ ​ ​ ​ ​ ​ ​[​i​m​a​g​e​V​i​e​w​ ​s​e​t​I​m​a​g​e​:​i​m​a​g​e​T​o​D​i​s​p​l​a​y​]​;​
 ​ ​ ​ ​}​ ​e​l​s​e​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​C​l​e​a​r​ ​t​h​e​ ​i​m​a​g​e​V​i​e​w​
 ​ ​ ​ ​ ​ ​ ​ ​[​i​m​a​g​e​V​i​e​w​ ​s​e​t​I​m​a​g​e​:​n​i​l​]​;​
 ​ ​ ​ ​}​
}​

Notice that if no image exists in the image store for that key (or there is no key for that possession), the pointer to the image will be nil. When the image is nil, the UIImageView just won’t display an image.

Build and run the application. Create a Possession and select it from the UITableView. Then, tap the camera button and take a picture. The image will appear as it should.

Dismissing the keyboard

When the keyboard appears on the screen in the possession detail view, it obscures ItemDetailViewController’s imageView. This is annoying when you’re trying to see an image, so you’re going to implement the delegate method textFieldShouldReturn: to have the text field resign its first responder status to dismiss the keyboard. (This is why you hooked up the delegate outlets earlier.) But first, in ItemDetailViewController.h, have ItemDetailViewController conform to the UITextFieldDelegate protocol.

@​i​n​t​e​r​f​a​c​e​ ​I​t​e​m​D​e​t​a​i​l​V​i​e​w​C​o​n​t​r​o​l​l​e​r​ ​:​ ​U​I​V​i​e​w​C​o​n​t​r​o​l​l​e​r​
 ​ ​ ​ ​<​U​I​N​a​v​i​g​a​t​i​o​n​C​o​n​t​r​o​l​l​e​r​D​e​l​e​g​a​t​e​,​ ​U​I​I​m​a​g​e​P​i​c​k​e​r​C​o​n​t​r​o​l​l​e​r​D​e​l​e​g​a​t​e​,​
 ​ ​ ​ ​ ​ ​ ​ ​U​I​T​e​x​t​F​i​e​l​d​D​e​l​e​g​a​t​e​>​

In ItemDetailViewController.m, implement textFieldShouldReturn:.

-​ ​(​B​O​O​L​)​t​e​x​t​F​i​e​l​d​S​h​o​u​l​d​R​e​t​u​r​n​:​(​U​I​T​e​x​t​F​i​e​l​d​ ​*​)​t​e​x​t​F​i​e​l​d​
{​
 ​ ​ ​ ​[​t​e​x​t​F​i​e​l​d​ ​r​e​s​i​g​n​F​i​r​s​t​R​e​s​p​o​n​d​e​r​]​;​
 ​ ​ ​ ​r​e​t​u​r​n​ ​Y​E​S​;​
}​

It would be stylish to also dismiss the keyboard if the user taps anywhere on ItemDetailViewController’s view. We can dismiss the keyboard by sending the view the message endEditing:. This message causes the text field (as a subview of the view) to resign as first responder. Now let’s figure out how to get the view to send a message when tapped.

We have seen how classes like UIButton can send an action message to a target when tapped. Buttons inherit this target-action behavior from their superclass, UIControl. You’re going to change the view of ItemDetailViewController from an instance of UIView to an instance of UIControl so that it can handle touch events.

In ItemDetailViewController.xib, select the main view instance. Open the identity inspector and change the view’s class to UIControl (Figure 13.11).

Figure 13.11  Changing the class of ItemDetailViewController’s view

Changing the class of ItemDetailViewController’s view

Then, open ItemDetailViewController.h in the assistant editor. Control-drag from the view (now a UIControl) to the method declaration area of ItemDetailViewController. When the pop-up window appears, select Action from the Connection pop-up menu. Notice that the interface of this pop-up window is slightly different than one you saw when creating and connecting the UIBarButtonItem. A UIBarButtonItem is a simplified version of UIControl – it only sends its target an action message when it is tapped. A UIControl, on the other hand, can send action messages on a variety of events.

Therefore, you must choose the appropriate event type to trigger the action message being sent. In this case, you want the action message to be sent when the user taps on the view. Configure this pop-up window to appear as it does in Figure 13.12 and click Connect.

Figure 13.12  Configuring a UIControl action

Configuring a UIControl action

This will create a stub method in ItemDetailViewController.m. Enter the following code into that method.

-​ ​(​I​B​A​c​t​i​o​n​)​b​a​c​k​g​r​o​u​n​d​T​a​p​p​e​d​:​(​i​d​)​s​e​n​d​e​r​
{​
 ​ ​ ​ ​[​[​s​e​l​f​ ​v​i​e​w​]​ ​e​n​d​E​d​i​t​i​n​g​:​Y​E​S​]​;​
}​

Build and run your application and test both ways of dismissing the keyboard.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.152.139