You’re ready to test out your image picker interface—for real. The simulator, unfortunately, does
not emulate the camera hardware nor does it come with any images in its photo library. To test this app, you’ll need to run it on a real iOS device. Since you’ve only built the iPhone interface, you’ll need an iPhone, iPodTouch, or something similar. If not, you’ll just have to read along until we get to the iPad interface.
Plug in your iPhone and set the project’s scheme to iPhone. Run it. Your app’s interface should look like that in Figure 7-12.
Figure 7-12. Testing the iPhone Interface
Tap an item, tap the placeholder image in the detail view, tap “Take a Picture”, and take a picture.
The cropped image should appear in the detail view, and again back in the master table, as shown in
Figure 7-12.
Congratulations, you’ve added picture taking to your app! You’re not quite done yet, but enjoy the moment and have fun with the camera.
Building the iPad Interface
The iPad interface is almost identical to the iPhone interface. Follow the steps back in the sections “Adding an Image View” and “Connecting a Choose Image Action” to add the view objects and the tap gesture recognizer, andconnect them all to the appropriate outlets and actions. (Don’t forget
to set the User Interaction Enabled property of the image view.) The only changes I suggest are to position the iPad image view on the left—it’s too far away from the label when centered—and change the size of the image view to 256x256, instead of 128x128. Your finished iPad interface should look like the one in Figure 7-13.
Figure 7-13. Finished iPad interface
Run the iPad version in the iPad simulator or on your iPad. If your iPad has a camera, that will work just fine. Picking an image from your photo library presents a ridiculously large interface that only works in portrait orientation,as shown in Figure 7-14.
Figure 7-14. iPad photo library picker
This, clearly, isn’t the ideal iPad interface. Consulting the documentation for
UIImagePickerController, you’ll find this statement:
On iPad, the correct way to present an image picker depends on its source type,
… if you specify a source type of UIImagePickerControllerSourceTypePhotoLibrary or UIImagePickerControllerSourceTypeSavedPhotosAlbum, you must present the image picker using apopover controller …
So while the full-screen camera interface works just fine on the iPad—and is the recommended interface—the photo library picker should be presented as a popover.
Presenting a view controller inside a popover is accomplished using a UIPopoverController object.
To use it, you must:
n create a popover controller for the view controller you want to display
n use the popover controller to present the interface
n dismiss the popover controller when finished
Adding a Popover
The first thing you’ll need is an instance variable where you can save a reference to the popover controller object. You’ll need to maintain a reference to the popover controller until you’re done with it. Add these three lines of boldcode to the private @interface MSDetailViewController () section at the beginning of the MSDetailViewController.m implementation file:
@interface MSDetailViewController ()
{
UIPopoverController *imagePopoverController;
}
@property (strong, nonatomic) UIPopoverController *masterPopoverController;
- (void)configureView;
- (void)presentImagePickerUsingCamera:(BOOL)useCamera;
- (void)dismissImagePicker;
@end
In the -presentImagePickerUsingCamera: method, replace the last line of code ([self presentViewController:cameraUI animated:YES completion:nil]) with this logic:
if (useCamera || UIDevice.currentDevice.userInterfaceIdiom==UIUserInterfaceIdiomPhone)
{
[self presentViewController:cameraUI animated:YES completion:nil];
} else
{
imagePopoverController = [[UIPopoverController alloc] initWithContentViewController:cameraUI]; [imagePopoverController presentPopoverFromRect:self.imageView.frame
inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
The new code first checks to see if the user wants to see the camera interface or if they’re running
on an iPhone. If either of these is true, present the cameraUI view controller as a full-screen interface, just as before.
If both of those conditions are false, then the user wants to pick an image from their photo library on an iPad. The else block creates and new UIPopoverController for the cameraUI view controller, and saves it in your new instance variable. It then uses the popover controller to present the picker interface. The FromRect: and inView: parameters anchor the popover to the image view.
Now find your -dismissImagePicker method and replace its code with the following:
if (imagePopoverController!=nil)
{
[imagePopoverController dismissPopoverAnimated:YES]; imagePopoverController = nil;
} else
{
[self dismissViewControllerAnimated:YES completion:nil];
}
The new code determines how -presentImagePickerUsingCamera: presented the picker interface—by examining the imagePopoverController variable—and dismissing the image picker in the same fashion it was presented.
There’s one tiny bit of housekeeping to do. At the very beginning of the
-presentImagePickerUsingCamera: method, add this line:
imagePopoverController = nil;
The reason this needs to be done is that the imagePopoverController variable is used as a flag to indicate which technique was used to present the image picker. If the user chooses an image,
or cancels, your delegate method is called, which ultimately results in the popover being dismissed and imagePopoverController being set to nil again.
The user can, however, also dismiss the iPad image picker by tapping outside of the popover. You could catch this by implementing a popover delegate and writing a
-popoverControllerDidDismissPopover: method—but that seems like a lot of work. It’s easier to just reset the variable before presenting the next interface, destroying any stray popover controller that might have been left over.
Your iPad photo library picker interface is now ready to use on the iPad, as shown in Figure 7-15.
Figure 7-15. iPad popover photo picker
Sticky Keyboards
One quirk of your app, if you haven’t noticed, is the sticky keyboard. No, I’m not talking about the kind you get from eating chocolate while programming. I’m talking about the virtual keyboard in iOS. Figure 7-16 shows thevirtual keyboard that appears when you tap inside a text field.
Figure 7-16. iOS’s virtual keyboard
The problem is that, once summoned, it won’t go away. It hangs around, covering up your image view, and generally being annoying. This has been a “feature” of iOS from its beginning, and it’s something you must deal withif it’s a problem for you app.
Now I’m sure you’ve noticed that many other apps you use don’t have this problem. Tapping outside of a text field makes the keyboard go away again. The authors of those apps intercept taps outside of the text field anddismiss the keyboard. There have been a wide variety of solutions to this problem, and you’ll find many of them floating around the Internet. I’m going to show you a particularly simple one that will only take a minute to add toyour app.
The “trick” is to catch touch events outside any of the text field objects and translate those events into an action that will retract the keyboard. Start with the second part first: create an action to retract the keyboard. In yourMSDetailViewController.m file, add the following method to your implementation:
- (IBAction)dismissKeyboard:(id)sender
{
[self.view endEditing:NO];
}
This simple method sends the -endEditing: message to the root view of your interface. The
-endEditing: method is ready-built to solve this problem; it searches through the view’s subviews looking for an editable object that's currently being edited. If it finds one, it asks the object to resign its first responder status,ending the editing session, and retracting the keyboard.
Now add a prototype for this method to your public @interface in MSDEtailViewController.h file, so Interface Builder can see it:
- (IBAction)dismissKeyboard:(id)sender;
Now you’re going to add another tap gesture recognizer. In the Main_iPhone.storyboard file, find the tap gesture recognizer in the object library. Drag one into your interface and drop it into the root view object, either by dropping itinto the empty space in the interface or directly into the root view object in the outline, as shown in Figure 7-17.
Figure 7-17. Adding a tap gesture recognizer to the root view
Control/right+click on the new gesture recognizer, drag it to the Detail View Controller, and connect it to the new -dismissKeyboard: action. (If you can’t figure out which gesture recognizer object belongs to the root view, use theconnections inspector (see Figure 7-9) in the section “Connecting a Choose Image Action.”) Now any tap that occurs outside a specific subview will pass those touch events to the root view, dismissing the keyboard. If you’renot sure why that happens, review the section “Hit Testing” in Chapter 4.
Give it a try. Run the iPhone interface, tap inside a text field, and then tap outside the text field. You should see the keyboard appear and then disappear.
Now add a tap gesture recognizer to the root view of the iPad interface (Main_iPad.storyboard) and connect it to the same action.
To be thorough, find the point in the -choosePicture: method where the app intends to present an interface, and add this one bold line of code:
[self dismissKeyboard:self];
if (hasPhotoLibrary && hasCamera)
{
This will cause the keyboard to retract when the user taps on the image view to change it. Remember that in hit testing, it’s the most specific view object that gets the touch events. Since the image view object receives touchevents, those events won’t make their way to the root view.
Advanced Camera Techniques
I’m sure you’re excited to add camera and photo library features to your app. If your goal, however, is to create the next Hipstamatic or Instagram, the UIImagePickerController isn’t what you want; you want the low-level camera controls.You’ll find that kind of control in the AVCaptureDevice class. That object represents a single image capture device (a.k.a. a camera), and gives you excruciatingly precise control over every aspect of it, from turning on the flash tocontrolling the white balance of the exposure.
This is part of the much larger AV Foundation framework, which also encompasses video capture, video playback, audio recording, and audio playback. You’ll explore some parts of this framework later in this book. Some ofits features are object-oriented, while others are C functions.
The advantage of using a class like UIImagePickerController is that so many of the picture-taking details are taken care of for you. But it also constrains your app’s functionality and design. The lower-level classes and functionsopen up a world of design and interface possibilities, but require that you deal with those details yourself. To learn more, start with the AV Foundation Programming guide you’ll find in Xcode’s Documentation and API reference.
Summary
Adding picture taking to your MyStuff app spiffed it up considerably and made it much more exciting to use. You also learned a little about presenting view controllers and manipulating images. You now know how to export an image tothe user’s camera roll, add tap gesture recognizers to an existing view, link to additional frameworks, and get that pesky keyboard out of the way. You’re also getting comfortable with outlets, connections, and delegates; in other words,you’re turning into an iOS developer!
Throughout the past few chapters, I’ve constantly referred to “view,” “controller,” and “data model” objects.
The next chapter is going to take another short recess from development to explain what that means and explore animportant design pattern.






No comments:
Post a Comment