Saturday, 26 April 2014

Testing the Camera [Smile!]

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 animages in its photo library. To test thiapp, you’ll need to run it on real iOS device. Since you’ve only built the iPhone interface, you’lneed aiPhone, iPodTouch, or something similar. If not, you’ll just have to read along until we geto the iPainterface.

Plug in your iPhone and set the projects scheme to iPhone. Run it. Your apps 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. (Dont 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—its 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, isnt 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.

Theres 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. Its 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 havent 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. iOSs virtual keyboard

The problem is that, once summoned, it wont 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 its something you must deal withif its a problem for you app.

Now I’m sure you’ve noticed that many other apps you use dont 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 views 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 cant 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, its the most specific view object that gets the touch events. Since the image view object receives touchevents, those events wont 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 tcreate the next Hipstamatic or Instagram, the UIImagePickerController isnt what you want; you wanthe low-level camera controls.You’ll find that kind of control in the AVCaptureDevicclass. That objecrepresents a single image capture device (a.k.a. a camera), and gives you excruciatingly precise controover every aspect of itfrom 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 apps 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 Xcodes Documentation and API reference.

Summary

Adding picture taking to your MyStuff app spiffed it up considerably and made it much more exciting tuse. You also learned a little about presenting view controllers and manipulating images. You now knohow to export an image tothe users camera roll, add tap gesture recognizers to an existing view, link tadditional frameworks, and gethat 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