Chapter 6
Integrating the Camera and Photo Library in iOS Apps

WHAT YOU LEARN IN THIS CHAPTER:

 
  • Taking pictures with the camera
  • Importing pictures from the Photo Library
  • Using gesture recognizers for advanced user interactions

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

You can find the wrox.com code downloads for this chapter at www.wrox.com/go/begiosprogramming on the Download Code tab. The code is in the chapter 06 download and individually named according to the names throughout the chapter.

Even before the iPhone came onto the market in 2007, camera phones were already gaining in popularity. Digital cameras had been around for a while and were far superior to any camera phone on the market, so many people owned both. This continued even after the release of the first iPhone, iPhone 3G, and iPhone 3GS. It began to change with improvements to the built-in camera in the iPhone 4. Photo-sharing services started releasing data showing that the most popular camera used to take pictures was the iPhone 4. Today, the camera on a smartphone has become one of the top selling points, with manufacturers building far superior cameras than digital cameras produced as recently as a few years ago.

Pictures can also add to the usability of an app. The Contacts app from Apple is a good example of this. When you add contacts, you have the option to assign a picture to them. When you receive a phone call from contacts, their picture displays on the screen. In the old days of landlines and caller ID, you needed to have the phone number of a person memorized to know who was calling. That eventually evolved to showing the name of the person or business the number was registered to, but that still involved reading. Glancing at your phone and seeing the face of the person who is calling can speed up recognition.

The Bands app adopts this idea by adding an optional picture to each band. Users can choose a picture from the photo library or take a picture with the camera. To implement this, you learn not only how to use the camera, but also how to add a UIImageView to the user interface and create gesture recognizers so that the user can interact with it.

ADDING AN IMAGE VIEW AND GESTURE RECOGNIZER

Before you add the code to choose or take pictures, first you need to add a place to display the image in the Band Details scene and give users a way to interact with it to set it. In iOS, images display using a UIImageView. As its name implies, a UIImageView is a subclass of UIView that displays an image. In code a UIImage represents the image. A UIImage can be a JPEG, a bitmap, a TIFF, an icon, a Windows cursor, or a PNG. Though not officially documented, the PNG format is the preferred format because it is a lossless format. This makes the image appear vivid on retina display devices.

Images and pictures come in an endless array of sizes. UIViews have a mode attribute that dictates how their contents display. If the content of the UIView is larger than the UIView, the system uses the mode to determine how to adjust the aspect ratio of the content. Because UIImageView is a subclass of UIView, it uses this mode to determine how to resize the image. Following are the modes:

 
  • Scale to Fill — Setting the mode to Scale to Fill alters the aspect ratio of the image to fill the entire UIImageView. This can result in images looking distorted.
  • Aspect Fit — The Aspect Fit mode keeps the same aspect ratios of the original image but resizes it so the entire image displays in the UIImageView. This can result in parts of the UIImageView being empty.
  • Aspect Fill — The Aspect Fill mode also resizes the image but ensures there is no empty space. This can result in parts of the image being outside the bounds of the UIImageView and therefore not shown.
  • Center — Center mode does not resize the image and simply centers the image in the UIImageView. With pictures taken from the camera, this can result in large portions of the picture not being shown.

For the Bands app you can use the Aspect Fit mode so that the entire picture is always visible.

The UIView class also has a userInteractionEnabled property that tells the system if the user can interact with it through touches. A UIView that has this property set to false never recognize touches. For example, a UIButton with an IBAction connected to it can never trigger the action if its userInteractionEnabled property is false. By default, a UIImageView has this attribute set to false.

Enabling User Interactions with a UIImageView

The Bands app can have an optional picture the user can set for each band. The first step in adding this functionality is to add a UIImageView to the Band Details scene. Because a UIImageView does not allow user interaction by default, you also need to set its userInteractionEnabled property to true. This is a simple check box in Interface Builder, as you see in the following Try It Out.

TRY IT OUT: Adding an Image View
 
  1. Select the Main.storyboard from the Project Navigator.
  2. In the Band Details scene, move the Name UILabel to the right 70 pixels to make room for the UIImageView.
  3. Resize the Name UITextField to be 210 pixels and aligned with the Name UILabel to finish making room for the UIImageView.
  4. Drag a new Image View from the Object library onto the view, and set its size to be 64 pixels by 64 pixels.
  5. Align it with the left guideline of the UIView and the top of the Name UILabel, and set its background color to light gray.
  6. Set the Mode of the UIImageView to Aspect Fit in the Attributes Inspector.
  7. Check the Allow User Interaction box in the Attributes Inspector.
  8. Drag a new UILabel onto the view, and set its boundaries to be the same as the UIImageView.
  9. In the Attributes Inspector, change its text to Add Photo, its alignment to Center using the center alignment button, and the number of lines to 2, as shown in Figure 6-1.
    阅读 ‧ 电子书库
    FIGURE 6-1
  10. Select the WBABandDetailsViewController.h file from the Project Navigator, and add IBOutlet properties for the UIImageView and UILabel using the following code:
    @property (nonatomic, assign) IBOutlet UIImageView *bandImageView;
    @property (nonatomic, assign) IBOutlet UILabel *addPhotoLabel;
  11. Open the Storyboard again and connect the bandImageView and addPhotoLabel properties in the WBABandDetailsViewController to the UIImageView and UILabel.
How It Works
What you did was make room for and then add the UIImageView you can use to not only display the picture for the band but also to set it. You also set its mode to Aspect Fit, so the entire picture displays. When checked, the User Interaction Allowed property in the Attributes Inspector sets the enableUserInteraction property of the UIImageView to true. This allows the UIImageView to accept touches from users. The default background for a UIImageView whose image property is not set is transparent. You change it to light gray so that the users know there is a user interface object there. Finally, you added a UILabel over the top of the UIImageView. The text for the label, “Add Photo,” lets the users know it’s a UIImageView and that they can interact with it. The text is the same as what users see in the Contacts app. Using the same text enables the users to easily recognize the purpose of that part of the user interface.

Learning About Gesture Recognizers

In previous chapters, you used IBActions to respond when the user taps various user interface objects. Not all user interface objects allow being connected to IBActions, even though all subviews of UIView can accept touches if their userInteractionEnable property is set to true. In the past, you would need to implement a series of delegate methods to track which subview is being touched and how many touches it received (which means how many fingers the user has touching the phone). The number of taps was given to you, so detecting that a subview was tapped twice with two fingers was relatively easy to implement but required a lot of code. Detecting if users swiped their finger across a subview was more difficult. First, you would need to do all the math to detect that a swipe had taken place as well as in which direction it went.

With the introduction of iPhoneOS 3.2, Apple added the UIGestureRecognizer classes to make these user interactions easier to implement. There are seven UIGestureRecognizer classes you can use, as listed in Table 6-1. You can add as many gesture recognizers to a single UIView as you want; though you need to keep the user experience in mind when doing so.

GESTURE RECOGNIZER DESCRIPTION

UITapGestureRecognizer Detects when the user taps a UIView. You can set how many touches and taps are required for the gesture to be recognized. For example, in the Maps app you can double-tap the map with one finger to zoom in. You can also double-tap the map with two fingers to zoom out.

UIPinchGestureRecognizer Detects when the user pinches two fingers together or spreads them apart. This is typically used for zooming in or out. In the Maps app you can pinch two fingers together to zoom out or you can spread two fingers apart to zoom in.

UIRotateGestureRecognizer Detects when the user uses two fingers moving in a circular motion. In the Maps app if you use two fingers and rotate them, the map rotates.

UISwipeGestureRecognizer Detects when the user moves any number of touches across the view in a particular direction. The best example of this is when you unlock an iOS device by swiping the screen from left to right.

UIPanGestureRecognizer Detects when the user drags any number of fingers around a view. In the Maps app you can pan around the map by touching the screen and dragging your finger.

UIScreenEdgePanGestureRecognizer Detects when the user begins a dragging gesture close to the edge of the screen. In iOS 7 you use this gesture from the bottom of the screen to bring up the Control Center.

UILongPressGestureRecognizer Detects when the user touches one or more fingers on the view and then holds the position for a set amount of time. An example of this is holding a finger down in the Maps app to add a pin to the map.

TABLE 6-1: Types of Gesture Recognizers

Some gesture recognizers have additional properties you can set (refer to Table 6-1). For example, the tap gesture can be set to recognize a minimum number of times the user taps the screen and also the minimum number of fingers doing the tapping. These default to one each, but you can adjust that using the numberOfTapsRequired and numberOfTouchesRequired properties. If the numberOfTapsRequired is two and the numberOfTouchesRequired is three, the user would need to double-tap the screen using three fingers. The swipe gesture has a direction property you need to set so that the system knows in which direction the user must swipe for the gesture to be recognized.

If a UIView has another UIView that overlaps it but does not recognize a particular gesture, the gesture is passed down to the UIView underneath it. In the following Try It Out you add both a UITapGestureRecognizer and a UISwipeGestureRecognizer to the UIImageView for the band picture. Those gestures can be recognized even though there is a UILabel on top of the UIImageView.

TRY IT OUT: Implementing Gesture Recognizers
 
  1. Select the WBABandDetailsViewController.h file from the Project Navigator, and add the following method declarations to the interface:
    - (void)bandImageViewTapDetected;
    - (void)bandImageViewSwipeDetected;
  2. Select the WBABandDetailsViewController.m file from the Project Navigator, and add the following code to the implementation:
    - (void)bandImageViewTapDetected
    {
        NSLog(@"band image tap detected");
    }

    - (void)bandImageViewSwipeDetected
    {
        NSLog(@"band image swipe detected");
    }
  3. Add the following code to the viewDidLoad method:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
       
        NSLog(@"titleLabel.text = %@", self.titleLabel.text);
       
        if(!self.bandObject)
            self.bandObject = [[WBABand alloc] init];

       
        [self setUserInterfaceValues];
       
       
    UITapGestureRecognizer *bandImageViewTapGestureRecognizer =
    [[UITapGestureRecognizer alloc] initWithTarget:self
    action:@selector(bandImageViewTapDetected)];
        bandImageViewTapGestureRecognizer.numberOfTapsRequired = 1;
        bandImageViewTapGestureRecognizer.numberOfTouchesRequired = 1;  
        [self.bandImageView addGestureRecognizer:bandImageViewTapGestureRecognizer];
       
        UISwipeGestureRecognizer *bandImageViewSwipeGestureRecognizer =
    [[UISwipeGestureRecognizer alloc] initWithTarget:self
    action:@selector(bandImageViewSwipeDetected)];
        bandImageViewSwipeGestureRecognizer.direction =
    UISwipeGestureRecognizerDirectionRight;
        [self.bandImageView addGestureRecognizer:bandImageViewSwipeGestureRecognizer];

    }
How It Works
You first declared two methods to the interface of the WBABandDetailsViewController named bandImageViewTapDetected and bandImageViewSwipeDetected. In their implementation you simply log a message to the debug console. This is just to verify that they are being called when the UIImageView is either tapped or swiped.
The main lesson of this Try It Out was creating the two UIGestureRecognizers. The first was the UITapGestureRecognizer. When you create it you use the initWithTarget:action: method. For the target you pass in self referring to the WBABandDetailsViewController. For the action you use the @selector followed by the bandImageViewTapDetected method name. This code tells the system that when the UITapGestureRecognizer is triggered, it should call the bandImageViewTapDetected method found in the WBABandDetailsViewController. This is the code equivalent of connecting an IBAction to a UIKit object. You then set the numberOfTapsRequired property and the numberOfTouchesRequired property both to 1. This tells the system to trigger the gesture when one finger taps once on the UIView to which the gesture is assigned. You assign the bandImageViewTapGestureRecognizer to the bandImageView by calling the addGestureRecognizer: method.
Next you declared a new UISwipeGestureRecognizer using the same initWithTarget:action: method but using the bandImageViewSwipeDetected method for the @selector. UISwipeGestureRecognizer requires setting its direction property, which you do using the UISwipeGestureRecognizerDirectionRight constant. You use the addGestureRecognizer method of the bandImageView again to add the gesture to the UIImageView. When users swipe from left to right across the bandImageView, the bandImageViewSwipeDetected method is called.

SELECTING A PICTURE FROM THE PHOTO LIBRARY

The Bands app gives users two ways to set the picture for a band. They can either choose a picture they have saved in the photo library or use the camera to take a picture. The photo library in the iOS simulator is empty by default. Before you start adding code to pick a photo, you need to add a photo to the library. The following Try It Out walks you through doing this in the iOS simulator.

NOTE Not all iOS devices have a camera. The iPhone has always had a camera, but early versions of the iPad and iPod touch did not. The simulator also does not support a camera. You can also restrict access to the camera using parental controls. All iOS devices do have a photo library to which the user can save pictures from e-mails and web pages.
TRY IT OUT: Save an Image from Safari to the Photo Library in the iPhone Simulator
 
  1. From the Xcode menu select Xcode ⇒ Open Developer Tool ⇒ iOS Simulator.
  2. Start Safari in the simulator.
  3. From the favorites menu, select ESPN or surf to any web page that has a picture on it.
  4. After the picture loads, long-press the picture to bring up the action sheet.
  5. Select Save Image from the action sheet.
  6. Go back to the home screen by selecting Hardware ⇒ Home from the menu.
  7. Open the Photos app. You’ll see the picture you just saved.
How It Works
iOS allows you to save pictures from other apps into your photo library. In this Try It Out you saved a picture from Mobile Safari using a UILongPressGestureRecognizer. You can also save pictures you receive in an e-mail or text message the same way. Third party apps can also implement a way to save pictures they display into the photo library. You will learn how to do this in Chapter 7, “Integrating Social Media.”

Learning About UIImagePickerController

You use UIImagePickerController to interact with both the camera and the photo library. This is the standard controller all apps use, so the user experience is the same no matter which app the user has open. The controller is self-contained but uses the UIImagePickerControllerDelegate to let your code know what image the user has selected or if the user canceled the action. Your app is responsible only for presenting and dismissing the controller.

For added security, Apple has accessibility controls around the photo library. This means users must explicitly grant your app access to their photo library before you can import images from it. When your app first presents the image picker and tries to access the library, users will be prompted to allow access, as shown in Figure 6-2.

阅读 ‧ 电子书库

FIGURE 6-2

If they deny access they will have no images to choose from. Instead users need to simply cancel the image picker. The next time the image picker is presented, it enables users to know that they have denied access for this app, as shown in Figure 6-3.

阅读 ‧ 电子书库

FIGURE 6-3

Determining Device Capabilities

Before presenting the image picker, you need to set its source type. The source type can be the camera, photo library, or saved photos album represented by the UIImagePickerControllerSourceTypeCamera, UIImagePickerControllerSourceTypePhotoLibrary, and the UIImagePickerControllerSourceTypeSavedPhotoAlbum enumeration values. If you try to present the image picker with a source type that is not supported by the device, your app will crash. Apple will test these scenarios when you submit your app for approval. Any crash will cause your app to be rejected.

To determine if a source type is available, you use the isSourceTypeAvailable: static method of the UIImagePickerController passing in the enumeration for the source type you would like to check. If the method returns true, you can present the controller with that source type. If it returns false you must do something different to prevent your app from crashing.

Allowing Picture Editing

A useful feature of the UIImagePickerController is that it enables users to move and scale the picture they selected before having it returned to your code. It’s a simple boolean you can set before presenting the picker, but it also means that you don’t have to implement your own editing interface. When an image is picked, the imagePickerController:didFinishPickingMediaWithInfo: method of the UIImagePickerControllerDelegate protocol is called. It has a dictionary that contains both the original image and the edited image, and information about what was edited. Table 6-2 lists the dictionary keys returned in the info dictionary.

INFO DICTIONARY KEY DESCRIPTION

UIImagePickerControllerMediaType The type of media picked. Its value is an NSString that will be either the kUTTypeImage or kUTTypeMovie constants.

UIImagePickerControllerOriginalImage A UIImage of the original image selected.

UIImagePickerControllerEditedImage A UIImage of the edited image.

UIImagePickerControllerCropRect An NSValue containing a CGRect that represents the rectangle used when editing and cropping the original image.

UIImagePickerControllerMediaURL An NSURL of the file system URL of the movie selected when the media type is kUTTypeMovie.

UIImagePickerControllerReferenceURL An NSURL of the file system URL of the original image or movie.

UIImagePickerControllerMediaMetadata An NSDictionary with the meta data associated with a new picture taken by the camera. This can be used to save the image to the photo library.

TABLE 6-2: Media Info Dictionary Keys

The following Try It Out demonstrates how this works by presenting the UIImagePickerController for the photo library.

TRY IT OUT: Displaying the Photo Library Image Picker
 
  1. Select the WBABandDetailsViewController.h file from the Project Navigator.
  2. Add the UIImagePickerControllerDelegate and UINavigationControllerDelegate protocols to the interface using the following code:
    @interface WBABandDetailsViewController : UIViewController <UITextFieldDelegate,
    UITextViewDelegate, UIActionSheetDelegate,
    UIImagePickerControllerDelegate, UINavigationControllerDelegate>
  3. Add the following method declaration to the interface:
    - (void)presentPhotoLibraryImagePicker;
  4. Select the WBABandDetailsViewController.m file from the Project Navigator.
  5. Add the following method to the implementation:
    - (void)presentPhotoLibraryImagePicker
    {
        UIImagePickerController *imagePickerController =
    [[UIImagePickerController alloc] init];
        imagePickerController.sourceType =
    UIImagePickerControllerSourceTypePhotoLibrary;
        imagePickerController.delegate = self;
        imagePickerController.allowsEditing = YES;
        [self presentViewController:imagePickerController animated:YES completion:nil];
    }
  6. Add the following UIImagePickerControllerDelegate methods to the implementation:
    - (void)imagePickerController:(UIImagePickerController *)picker
    didFinishPickingMediaWithInfo:(NSDictionary *)info
    {
        UIImage *selectedImage =
    [info objectForKey:UIImagePickerControllerEditedImage];
        if(selectedImage == NULL)
            selectedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
       
        self.bandImageView.image = selectedImage;
        self.addPhotoLabel.hidden = YES;
       
        [picker dismissViewControllerAnimated:YES completion:nil];
    }

    - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
    {
        [picker dismissViewControllerAnimated:YES completion:nil];
    }
  7. Modify the bandImageViewTapDetected method with the following code:
    - (void)bandImageViewTapDetected
    {
        if([UIImagePickerController
    isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
        {
            [self presentPhotoLibraryImagePicker];
        }
        else
        {
            UIAlertView *photoLibraryErrorAlert = [[UIAlertView alloc]
    initWithTitle:@"Error" message:@"There are no" delegate:nil
    cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [photoLibraryErrorAlert show];
        }
    }
  8. Run the app in the iPhone 4-inch simulator. Tapping the empty image view now shows the Photo Library Image Picker.
How It Works
You first declared that the WBABandDetailsViewController implements both the UIImagePickerControllerDelegate and the UINavigationControllerDelegate. You needed to declare the UINavigationControllerDelegate because the UIImagePickerController implements it. You did not need to implement any of its methods, but you will get a build error if it is not present.
Next, you declared and implemented a method to show the UIImagePickerController with its source type set to UIImagePickerControllerSourceTypePhotoLibrary. You also set its delegate to the WBABandDetailsViewController using self and set the allowsEditing flag to true before presenting the UIImagePickerController modally over the WBABandDetailsViewController.
Then you implemented the two methods of the UIImagePickerControllerDelegate. The imagePickerController:didFinishPickingMediaWithInfo: method is called when users select an image from the photo library. You get the image by first looking at the UIImagePickerControllerEditedImage value in the media info NSDictionary. If this value is NULL, you then get the image from the UIImagePickerControllerOriginalImage value. Using that image, you set the UIImageView and hide the addPhotoLabel using its hidden property before dismissing the UIImagePickerController.
The other delegate method you implemented, imagePickerControllerDidCancel:, gets called when the user cancels the UIImagePickerController, in which case you simply dismiss it.
The last thing you did was modifying the bandImageViewTapDetectedMethod. In the new code you first make sure the photo library is available on the device. If it is, call the presentPhotoLibraryImagePicker to display the UIImagePickerController; if not, notify the user that no photo library is available.

Saving Band Images

You can now present the image picker for the photo library, choose an image, and set the image property of the UIImageView with the picture. You need to add the code to save the picture with the rest of the WBABand instance in standardUserDefaults. Similar to the WBABand instance, you cannot save a UIImage directly into standardUserDefaults. You first need to serialize it into an NSData object. You can use one of two helper functions to do this. The UIImageJPEGRepresentation can take the UIImage and a compression ratio to serialize the picture in the JPEG format. In the Bands app you can use the UIImagePNGRepresentation method instead. It takes the UIImage and serializes it into the PNG format. After the image is converted to NSData, it can be saved in standardUserDefaults with the rest of the WBABand instance. To load the picture back, you simply retrieve the NSData and create the UIImage with the initWithData method.

TRY IT OUT: Saving Images in NSUserDefaults
 
  1. Select the WBABand.h file from the Project Navigator, and add the following property:
    @property (nonatomic, strong) UIImage *bandImage;
  2. Select the WBABand.m file from the Project Navigator.
  3. Add the following key with the other static keys prior to the implementation:
    static NSString *bandImageKey = @"BABandImageKey";
  4. Modify the initWithCoder: method with the following code:
    -(id) initWithCoder:(NSCoder *)coder
    {
        self = [super init];
       
        self.name = [coder decodeObjectForKey:nameKey];
        self.notes = [coder decodeObjectForKey:notesKey];
        self.rating = [coder decodeIntegerForKey:ratingKey];
        self.touringStatus = [coder decodeIntegerForKey:tourStatusKey];
        self.haveSeenLive = [coder decodeBoolForKey:haveSeenLiveKey];
       
       
    NSData *bandImageData = [coder decodeObjectForKey:bandImageKey];
        if(bandImageData)
        {
            self.bandImage = [UIImage imageWithData:bandImageData];
        }

       
        return self;
    }
  5. Modify the encodeWithCoder: method with the following code:
    - (void)encodeWithCoder:(NSCoder *)coder
    {
        [coder encodeObject:self.name forKey:nameKey];
        [coder encodeObject:self.notes forKey:notesKey];
        [coder encodeInteger:self.rating forKey:ratingKey];
        [coder encodeInteger:self.touringStatus forKey:tourStatusKey];
        [coder encodeBool:self.haveSeenLive forKey:haveSeenLiveKey];
       
       
    NSData *bandImageData = UIImagePNGRepresentation(self.bandImage);
        [coder encodeObject:bandImageData forKey:bandImageKey];

    }
  6. Select the WBABandDetailsViewController.m file from the Project Navigator.
  7. Modify the setUserInterfaceValues with the following code:
    - (void)setUserInterfaceValues
    {
        self.nameTextField.text = self.bandObject.name;
        self.notesTextView.text = self.bandObject.notes;
        self.ratingStepper.value = self.bandObject.rating;
        self.ratingValueLabel.text = [NSString stringWithFormat:@"%g",
    self.ratingStepper.value];
        self.touringStatusSegmentedControl.selectedSegmentIndex =
    self.bandObject.touringStatus;
        self.haveSeenLiveSwitch.on = self.bandObject.haveSeenLive;
       
       
    if(self.bandObject.bandImage)
        {
            self.bandImageView.image = self.bandObject.bandImage;
            self.addPhotoLabel.hidden = YES;
        }

    }
  8. Modify the imagePickerController:didFinishPickingMediaWithInfo: method with the following code:
    - (void)imagePickerController:(UIImagePickerController *)picker
    didFinishPickingMediaWithInfo:(NSDictionary *)info
    {
        UIImage *selectedImage =
    [info objectForKey:UIImagePickerControllerEditedImage];
        if(!selectedImage)
            selectedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
       
        self.bandImageView.image = selectedImage;
       
    self.bandObject.bandImage = selectedImage;
        self.addPhotoLabel.hidden = YES;
       
        [picker dismissViewControllerAnimated:YES completion:nil];
    }
  9. Run the app in the iPhone 4-inch simulator. Pictures you assign to a band will now be persisted.
How It Works
You first added a new UIImage property named bandImage to the WBABand class. When the users add a new band, the WBABand instance they created gets added to the data model and saved to standardUserDefaults using the savedBandsDictionary method that was implemented in Chapter 5, “Using Table Views.” In order for the bandImage to be saved, it needs to be added to the encodeUsingCoder: and decodeUsingCoder: methods in the WBABand implementation. To archive a UIImage, it needs to be serialized to an NSData variable. You use the UIImagePNGRepresentation function to do this. You can then add it to the coder in the encodeUsingCoder: method using the new bandImageKey you added to the implementation. When a WBABand instance is retrieved from standardUserDefaults it calls decodeUsingCoder:. In this method you first get the NSData variable back from the code using the same bandImageKey. Finally you set the bandImage property using the imageWithData: method of the UIImageClass.
In the WBABandDetailsViewController you modified the setUserInterfaceValues to set the bandImageView with the bandImage property of the bandObject, if it exists, and also hide the addPhotoLabel. Finally, you added the code to set the bandImage property of the bandObject when the user picks a new image.

Deleting Band Images

For a robust user experience, you should give users the ability to delete a picture they already have set for a band. You can use a UISwipeGestureRecognizer to do this. As discussed in Chapter 4, “Creating a User Input Form,” when you delete data you should prompt users to make sure it’s what they actually want to do. When deleting a band, you implemented a UIActionSheet with a destructive button. You also implemented the UIActionSheetDelegate in the WBABandDetailsViewController to handle whichever option users selected.

When users swipe to delete the band picture, you should again use a UIActionSheet with a destructive button. Because the WBABandDetailsViewController already implements the UIActionSheetDelegate, you need to know which action sheet users interact with. The UIActionSheet has a tag property, which is an integer. You can set the tag property then check it in UIActionSheetDelegate methods to determine the context of the UIActionSheet.

To make your code easier to follow, you should declare the values you will use for the tag property as a constant. There are a couple ways to do this. One way is to use the #define C preprocessor command. The Coding Guidelines for Cocoa (which you can find at https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines) discourage this approach, although many developers use it. The recommended approach is to use an enumeration, which you will use in the following Try It Out.

TRY IT OUT: Implementing the Swipe to Delete Gesture
 
  1. Select the WBABandDetailsViewController.h file from the Project Navigator.
  2. Add the following code before the interface:
    typedef enum {
        WBAActionSheetTagDeleteBand,
        WBAActionSheetTagDeleteBandImage,
    } WBAActionSheetTag;
  3. Select the WBABandDetailsViewController.m file from the Project Navigator.
  4. Modify the bandImageViewSwipeDetected method with the following code:
    - (void)bandImageViewSwipeDetected
    {
        if(self.bandObject.bandImage)
        {
            UIActionSheet *deleteBandImageActionSheet =
    [[UIActionSheet alloc] initWithTitle:nil delegate:self
    cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete Picture"
    otherButtonTitles:nil];
            deleteBandImageActionSheet.tag = WBAActionSheetTagDeleteBandImage;
            [deleteBandImageActionSheet showInView:self.view];
        }
    }
  5. Modify the actionSheet:clickedButtonAtIndex: method with the following code:
    - (void)actionSheet:(UIActionSheet *)actionSheet
    clickedButtonAtIndex:(NSInteger)buttonIndex
    {
        if(actionSheet.tag == WBAActionSheetTagDeleteBandImage)
        {
            if(buttonIndex == actionSheet.destructiveButtonIndex)
            {
                self.bandObject.bandImage = nil;
                self.bandImageView.image = nil;
                self.addPhotoLabel.hidden = NO;
            }
        }
        else if(actionSheet.tag == WBAActionSheetTagDeleteBand)
        {
            if(actionSheet.destructiveButtonIndex == buttonIndex)
            {
                self.bandObject = nil;
                self.saveBand = NO;
               
                if(self.navigationController)
                    [self.navigationController popViewControllerAnimated:YES];
                else
                    [self dismissViewControllerAnimated:YES completion:nil];
            }
        }
    }
  6. Run the code in the iPhone 4-inch simulator. Swiping the image view now prompts you to delete the picture.
How It Works
You added a new enumeration named WBAActionSheetTag with two values, WBAActionSheetTagDeleteBand and WBAActionSheetTagDeleteBandImage, to the interface file of the WBABandDetailsViewController class to use with the tag property of a UIActionSheet when prompting the users.
You then modified the bandImageViewSwipeDetected method to show a UIActionSheet with its tag set to WBAActionSheetTagDeleteBandImage. This prompts users to verify that they want to delete the picture.
In the actionSheet:clickedButtonAtIndex: method of the UIActionSheetDelegate protocol, you can now use the tag to determine the context in which the UIActionSheet was displayed. If its tag is WBAActionSheetTagDeleteBandImage, you know the user has swiped the band picture. If the buttonIndex is the destructiveButtonIndex, you know the user has confirmed they would like to delete the band picture. You set both the image property of the bandImageView and the bandImage property of the bandObject to nil. Finally you set the hidden property of the addPhotoLabel back to false so that it again is shown to the users.

TAKING A PICTURE WITH THE CAMERA

Presenting the UIImagePickerController to use the camera is similar to presenting it for the photo library. You first check to make sure the camera is available on the device and simply change the sourceType of the UIImagePickerController to UIImagePickerControllerSourceTypeCamera. Just because the device has a camera does not mean that is what users want to use to set the band picture. They may still want to select a picture in their photo library.

Unfortunately, Apple does not have a built-in way to prompt users if they want to choose a saved picture or take a new picture. Most apps that enable both have adopted prompting users with a UIActionSheet much like you have already implemented when users try to delete something, as you will see in the following Try It Out.

TRY IT OUT: Presenting the Camera
 
  1. Select the WBABandDetailsViewController.h file from the Project Navigator.
  2. Modify the WBAActionSheetTag enum with the following code:
    typedef enum {
        WBAActionSheetTagDeleteBand,
        WBAActionSheetTagDeleteBandImage,
        WBAActionSheetTagChooseImagePickerSource,
    } WBAActionSheetTag;
  3. Add a new enum using the following code:
    typedef enum {
        WBAImagePickerSourceCamera,
        WBAImagePickerSourcePhotoLibrary
    } WBAImagePickerSource;
  4. Add the following method declaration:
    - (void)presentPhotoLibraryImagePicker;
  5. Select the WBABandDetailsViewController.m file from the Project Navigator.
  6. Modify the bandImageViewTapDetected method with the following code:
    - (void)bandImageViewTapDetected
    {
        if([UIImagePickerController
    isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
        {
            UIActionSheet *chooseCameraActionSheet = [[UIActionSheet alloc]
    initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel"
    destructiveButtonTitle:nil otherButtonTitles:@"Take with Camera",
    @"Choose from Photo Library", nil];
            chooseCameraActionSheet.tag = WBAActionSheetTagChooseImagePickerSource;
            [chooseCameraActionSheet showInView:self.view];
        }
        else if([UIImagePickerController
    isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
        {
            [self presentPhotoLibraryImagePicker];
        }
        else
        {
            UIAlertView *photoLibraryErrorAlert = [[UIAlertView alloc]
                        initWithTitle:@"Error" message:@"There are no" delegate:nil
                        cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [photoLibraryErrorAlert show];
        }
    }
 
  1. Modify the actionSheet:clickedButtonAtIndex: method with the following code:
    - (void)actionSheet:(UIActionSheet *)actionSheet
    clickedButtonAtIndex:(NSInteger)buttonIndex
    {
        if(actionSheet.tag == WBAActionSheetTagChooseImagePickerSource)
        {
            if(buttonIndex == WBAImagePickerSourceCamera)
            {
                [self presentCameraImagePicker];
            }
            else if (buttonIndex == WBAImagePickerSourcePhotoLibrary)
            {
                [self presentPhotoLibraryImagePicker];
            }
        }
        else if(actionSheet.tag == WBAActionSheetTagDeleteBandImage)
        {
            if(buttonIndex == actionSheet.destructiveButtonIndex)
            {
                self.bandObject.bandImage = nil;
                self.bandImageView.image = nil;
                self.addPhotoLabel.hidden = NO;
            }
        }
        else if(actionSheet.tag == WBAActionSheetTagDeleteBand)
        {
            if(buttonIndex == actionSheet.destructiveButtonIndex)
            {
                self.bandObject = nil;
                self.saveBand = NO;
               
                if(self.navigationController)
                    [self.navigationController popViewControllerAnimated:YES];
                else
                    [self dismissViewControllerAnimated:YES completion:nil];
            }
        }
    }
  2. Add the following code to the implementation:
    - (void)presentCameraImagePicker
    {
        UIImagePickerController *imagePickerController =
    [[UIImagePickerController alloc] init];
        imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
        imagePickerController.delegate = self;
        imagePickerController.allowsEditing = YES;
        [self presentViewController:imagePickerController
    animated:YES completion:nil];
    }
  3. Run the app on a test device with a camera. When you tap the image view, you’re prompted to take a picture with the camera or choose from the library. Selecting Take with Camera allows you to take a new picture using the camera.
How It Works
You first added a WBAActionSheetTagChooseImagePickerSource value to the WBAActionSheetTag enumeration. You also added a new enumeration named WBAImagePickerSource to keep track of the button index of the options shown when prompting users with devices that have both a camera and a photo library. It has two values, WBAImagePickerSourceCamera and WBAImagePickerSourcePhotoLibrary. You then declared a new method in the interface named presentCameraImagePicker.
In the implementation you modified the bandImageViewTapDetected method to first check if the device has a camera by using the isSourceTypeAvailable: static method of the UIImagePickerController class and the UIImagePickerControllerSourceTypeCamera constant. If it returns true, you prompt the users to choose either the camera or the photo library using a new UIActionSheet with its tag property set to WBAActionSheetTagChooseImagePickerSource. This UIActionSheet is different from ones you have used before. There is no need for a destructive button, so you set the destructiveButtonTitle argument to nil when creating the UIActionSheet. To add the “Take with Camera” and “Choose from Photo Library” buttons, you pass them in using a C Style array in the otherButtonTitles argument. A C Style array lists all of the values followed by a nil.
In the actionSheet:didClickButtonAtIndex: method you added code to handle a UIActionSheet with its tag set to WBAActionSheetTagChooseImagePickerSource. If the buttonIndex is equal to WBAImagePickerSourceCamera, the code calls the presentCameraImagePicker method. If the buttonIndex is equal to WBAImagePickerSourcePhotoLibrary, the code calls the presentPhotoLibraryImagePicker method.
Finally you implemented the presentCameraImagePicker method. It creates and shows the UIImagePickerController virtually the same as the presentPhotoLibraryImagePicker, except it sets the sourceType to UIImagePickerControllerSourceTypeCamera. When the UIImagePickerController is displayed the users can now take a picture using the camera of the device.
NOTE To test the camera code, you need to test on a device that has a camera. There is no other way to test this code. Though it can be a barrier, you need to test any app you write on a device prior to submitting it for approval, so it’s a good habit to get into as early in the development process as possible.

SUMMARY

The camera and the photo library are valuable features of iOS devices. They can help you add to the user experiences of your apps, but you need to make sure that the device on which your app runs supports them.

In this chapter you learned how to add a UIImageView to your user interface and how to implement a UITapGestureRecognizer and UISwipeGestureRecognizer so that users can interact with it. You also learned how to use the UIImagePickerController to check what capabilities the device has and to also interact with the camera or photo library to set the picture associated with a band.

While implementing this feature, you learned more about using the UIActionSheet. You learned how to hide the destructive button and add your own options as well as how to tell the context in which the UIActionSheet was shown when the actionSheet:didClickButtonAtIndex: method of the UIActionSheetDelegate protocol gets called. In the next chapter you will expand on this by giving users options to share their bands using e-mail, text messaging, and social media.

EXERCISES
 
  1. How would you change the tap gesture recognizer to require two fingers tapping the image to set the band picture?
  2. What are the three source types of the UIImagePickerController?
  3. What happens if you try to present the image picker on a device that does not support the source type?
  4. What property can you set on a UIActionSheet so that your delegate method knows how to handle the button index that was clicked?
WHAT YOU LEARNED IN THIS CHAPTER
TOPIC KEY CONCEPTS
UIImageView The UIImageView object is the UIKit object used to display images in an iOS app. The mode property of the UIImageView determines the aspect ratios to use when an image is too large to be displayed.
UIGestureRecognizer The userInteractionEnabled property of the UIView class tells the system whether or not it can accept interactions for a user. When it is enabled you can use the various UIGestureRecognizers in UIKit such as tapping, swiping, pinching, and panning.
UIImagePickerController In all iOS apps you interact with the device’s photo library and camera using the UIImagePickerController. Its implementation is self-contained, which allows users to not only select an image saved on their device or take a new picture with the camera, but also to edit the picture before returning it to your code. You get the image back using the UIImagePickerControllerDelegate protocol methods.
UIActionSheet tag property The best way to prompt a user to select from a list of options is to use the UIActionSheet. A UIViewController may have many reasons to prompt a user, but a UIActionSheet has only one delegate. In order to know what context the UIActionSheet was shown in, you can use its tag property.