Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (12.71 MB, 929 trang )
As a popover
This is the expected approach on the iPad, according to the documentation.
To let the user choose an item from the photo library, instantiate UIImagePicker‐
Controller and assign its sourceType one of these values:
UIImagePickerControllerSourceTypeSavedPhotosAlbum
The user is confined to the contents of the Camera Roll / Saved Photos album.
UIImagePickerControllerSourceTypePhotoLibrary
The user is shown a table of all albums, and can navigate into any of them.
You should call the class method isSourceTypeAvailable: beforehand; if it doesn’t
return YES, don’t present the controller with that source type.
You’ll probably want to specify an array of mediaTypes you’re interested in. This array
will usually contain kUTTypeImage, kUTTypeMovie, or both; or you can specify all avail‐
able types by calling the class method availableMediaTypesForSourceType:.
After doing all of that, and having supplied a delegate (adopting UIImagePicker‐
ControllerDelegate and UINavigationControllerDelegate), present the view controller.
Here’s a complete example for the iPhone:
UIImagePickerControllerSourceType type =
UIImagePickerControllerSourceTypePhotoLibrary;
BOOL ok = [UIImagePickerController isSourceTypeAvailable:type];
if (!ok) {
NSLog(@"alas");
return;
}
UIImagePickerController* picker = [UIImagePickerController new];
picker.sourceType = type;
picker.mediaTypes =
[UIImagePickerController availableMediaTypesForSourceType:type];
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil]; // iPhone
Your app is now attempting to access the photo library. The very first time your app
does that, a system alert will appear, prompting the user to grant your app permission
(Figure 17-1). You can modify the body of this alert by setting the “Privacy — Photo
Library Usage Description” key (NSPhotoLibraryUsageDescription) in your app’s
Info.plist to tell the user why you want to access the photo library. This is a kind of
“elevator pitch”; you need to persuade the user in very few words.
If the user denies your app access, you’ll still be able to present the UIImagePicker‐
Controller, but it will be empty (with a reminder that the user has denied your app access
to the photo library) and the user won’t be able to do anything but cancel (Figure 17-2).
Thus, your code is unaffected. You can check beforehand to learn whether your app has
access to the photo library — I’ll explain how later in this chapter — and opt to do
694
|
Chapter 17: Photo Library and Image Capture
www.it-ebooks.info
Figure 17-1. The system prompts for photo library access
Figure 17-2. The image picker, when the user has denied access
something other than present the UIImagePickerController if access has been denied;
but you don’t have to, because the user will see a coherent interface and will cancel, and
your app will proceed normally from there.
To retest the system alert and other access-related behaviors, go to
the Settings app and choose General → Reset → Reset Location &
Privacy. This, unfortunately, causes the system to revert to its de‐
fault settings for everything in the Privacy section of Settings: Loca‐
tion Services and all System Services will be On, and all permis‐
sions lists will be empty.
If the user does what Figure 17-2 suggests, switching to the Set‐
tings app and enabling access for your app under Privacy → Pho‐
tos, your app will be terminated in the background! This is unfor‐
tunate, but is probably not a bug; Apple presumably feels that in this
situation your app cannot continue coherently and should start over
from scratch.
Choosing From the Photo Library
www.it-ebooks.info
|
695
On the iPhone, the delegate will receive one of these messages:
• imagePickerController:didFinishPickingMediaWithInfo:
• imagePickerControllerDidCancel:
On the iPad, if you’re using a popover, there’s no Cancel button, so there’s no imagePickerControllerDidCancel:; you can detect the dismissal of the popover through
the popover delegate. With a presented view controller, if a UIImagePickerController‐
Delegate method is not implemented, the view controller is dismissed automatically at
the point where that method would be called; but rather than relying on this, you should
probably implement both delegate methods and dismiss the view controller yourself in
each.
The didFinish... method is handed a dictionary of information about the chosen
item. The keys in this dictionary depend on the media type:
An image
The keys are:
UIImagePickerControllerMediaType
A UTI; probably @"public.image", which is the same as kUTTypeImage.
UIImagePickerControllerReferenceURL
An ALAsset URL pointing to the original image file in the library.
UIImagePickerControllerOriginalImage
A UIImage. This is the output you are expected to use. For example, you might
display it in a UIImageView.
A movie
The keys are:
UIImagePickerControllerMediaType
A UTI; probably @"public.movie", which is the same as kUTTypeMovie.
UIImagePickerControllerReferenceURL
An ALAsset URL pointing to the original movie file in the library.
UIImagePickerControllerMediaURL
A file URL to a copy of the movie saved into a temporary directory. This is the
output you are expected to use. For example, you might display it in an
MPMoviePlayerController or an AVPlayerLayer (Chapter 15).
Optionally, you can set the view controller’s allowsEditing to YES. In the case of an
image, the interface then allows the user to scale the image up and to move it so as to
be cropped by a preset rectangle; the dictionary will include two additional keys:
696
| Chapter 17: Photo Library and Image Capture
www.it-ebooks.info
UIImagePickerControllerCropRect
An NSValue wrapping a CGRect.
UIImagePickerControllerEditedImage
A UIImage. This becomes the image you are expected to use.
In the case of a movie, if the view controller’s allowsEditing is YES, the user can trim
the movie just as with a UIVideoEditorController (Chapter 15). The dictionary keys are
the same as before.
Here’s an example implementation of imagePickerController:didFinishPickingMediaWithInfo: that covers the fundamental cases:
-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info {
NSURL* url = info[UIImagePickerControllerMediaURL];
UIImage* im = info[UIImagePickerControllerOriginalImage];
UIImage* edim = info[UIImagePickerControllerEditedImage];
if (edim)
im = edim;
if (!self.currentPop) { // presented view
[self dismissViewControllerAnimated:YES completion:nil];
}
else { // popover
[self.currentPop dismissPopoverAnimated:YES];
self.currentPop = nil;
}
NSString* type = info[UIImagePickerControllerMediaType];
if ([type isEqualToString: (NSString*)kUTTypeImage] && im)
[self showImage:im];
else if ([type isEqualToString: (NSString*)kUTTypeMovie] && url)
[self showMovie:url];
}
Assets Library Framework
The Assets Library framework does for the photo library roughly what the Media Player
framework does for the music library (Chapter 16), letting your code explore the li‐
brary’s contents. One obvious use of the Assets Library framework might be to imple‐
ment your own interface for letting the user choose an image, in a way that transcends
the limitations of UIImagePickerController. But you can go further with the photo
library than you can with the media library: you can save media into the Camera Roll /
Saved Photos album, and you can even create a new album and save media into it.
A photo or video in the photo library is an ALAsset. Like a media entity, an ALAsset
can describe itself through key–value pairs called properties. (This use of the word
“properties” has nothing to do with Objective-C language properties.) For example, it
can report its type (photo or video), its creation date, its orientation if it is a photo whose
Assets Library Framework
www.it-ebooks.info
|
697
metadata contains this information, and its duration if it is a video. You fetch a property
value with valueForProperty:. The properties have names like ALAssetPropertyType.
A photo can provide multiple representations (roughly, image file formats). A given
photo ALAsset lists these representations as one of its properties, ALAssetPropertyRepresentations, an array of strings giving the UTIs identifying the file formats; a
typical UTI might be @"public.jpeg" (kUTTypeJPEG). A representation is an ALAsset‐
Representation. You can get a photo’s defaultRepresentation, or ask for a particular
representation by submitting a file format’s UTI to representationForUTI:.
Once you have an ALAssetRepresentation, you can interrogate it to get the actual image,
either as raw data or as a CGImage (see Chapter 2). The simplest way is to ask for its
fullResolutionImage or its fullScreenImage (the latter is more suitable for display
in your interface, and is identical to what the Photos app displays); you may then want
to derive a UIImage from this using imageWithCGImage:scale:orientation:. The
original scale and orientation of the image are available as the ALAssetRepresentation’s
scale and orientation. Alternatively, if all you need is a small version of the image to
display in your interface, you can ask the ALAsset itself for its aspectRatioThumbnail. An ALAssetRepresentation also has a url, which is the unique identifier for
the ALAsset.
The photo library itself is an ALAssetsLibrary instance. It is divided into groups
(ALAssetsGroup), which have types. For example, the user might have multiple albums;
each of these is a group of type ALAssetsGroupAlbum. You also have access to the Photo‐
Stream album. An ALAssetsGroup has properties, such as a name, which you can fetch
with valueForProperty:; one such property, the group’s URL (ALAssetsGroupPropertyURL), is its unique identifier. To fetch assets from the library, you either fetch
one specific asset by providing its URL, or you can start with a group, in which case you
can then enumerate the group’s assets. To obtain a group, you can enumerate the library’s
groups of a certain type, in which case you are handed each group as an ALAssetsGroup,
or you can provide a particular group’s URL. Before enumerating a group’s assets, you
may optionally filter the group using a simple ALAssetsFilter; this limits any subsequent
enumeration to photos only, videos only, or both.
The Assets Library framework uses Objective-C blocks for fetching and enumerating
assets and groups. These blocks behave in a special way: at the end of the enumeration,
they are called one extra time with a nil first parameter. Thus, you must code your block
carefully to avoid treating the first parameter as real on that final call. I was initially
mystified by this curious block enumeration behavior, but one day the reason for it came
to me in a flash: these blocks are all called asynchronously (on the main thread), meaning
that the rest of your code has already finished running, so you’re given an extra pass
through the block as your first opportunity to do something with all the data you’ve
presumably gathered in the previous passes.
698
|
Chapter 17: Photo Library and Image Capture
www.it-ebooks.info
As I mentioned in the previous section, the system will ask the user for permission the
first time your app tries to access the photo library, and the user can refuse. You can
learn directly beforehand whether access has been refused:
ALAuthorizationStatus stat = [ALAssetsLibrary authorizationStatus];
if (stat == ALAuthorizationStatusDenied ||
stat == ALAuthorizationStatusRestricted) {
NSLog(@"%@", @"No access");
return;
}
There is, however, no need to do this, because all the block-based methods for accessing
the library allow you to supply a failure block; thus, your code will be able to retreat in
good order when it discovers that it can’t access the library.
We now know enough for an example! Given an album title, I’ll find that album, pull
out the first photo, and display that photo in the interface. The first step is to find the
album; I do that by cycling through all albums, stopping when I find the one whose title
matches the target title. The block will then be called one last time; at that point, I call
another method to pull the first photo out of that album:
- (void) findAlbumWithTitle: (NSString*) albumTitle {
__block ALAssetsGroup* album = nil;
ALAssetsLibrary* library = [ALAssetsLibrary new];
[library enumerateGroupsWithTypes: ALAssetsGroupAlbum
usingBlock: ^ (ALAssetsGroup *group, BOOL *stop) {
if (group) {
NSString* title =
[group valueForProperty: ALAssetsGroupPropertyName];
if ([title isEqualToString: albumTitle]) {
album = group;
*stop = YES;
}
} else { // afterward
if (!album) {
NSLog(@"%@", @"failed to find album");
return;
}
[self showFirstPhotoOfGroup:album];
}
}
failureBlock: ^ (NSError *error) {
NSLog(@"oops! %@", [error localizedDescription]);
// e.g. "Global denied access"
}
];
}
And here’s the second method; it starts to enumerate the items of the album, stopping
immediately after the first photo and showing that photo in the interface. I don’t need
a very big version of the photo, so I use the asset’s aspectRatioThumbnail:
Assets Library Framework
www.it-ebooks.info
|
699
- (void) showFirstPhotoOfGroup: (ALAssetsGroup*) group {
__block ALAsset* photo;
[group enumerateAssetsUsingBlock:
^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
NSString* type = [result valueForProperty:ALAssetPropertyType];
if ([type isEqualToString: ALAssetTypePhoto]) {
photo = result;
*stop = YES;
}
} else { // afterward
if (!photo)
return;
CGImageRef im = photo.aspectRatioThumbnail;
UIImage* im2 = [UIImage imageWithCGImage:im scale:0
orientation:UIImageOrientationUp];
self.iv.image = im2; // put image into our UIImageView
}
}];
}
You can write files into the Camera Roll / Saved Photos album. The basic function for
writing an image file to this location is UIImageWriteToSavedPhotosAlbum. Some kinds
of video file can also be saved here; in an example in Chapter 15, I checked whether this
was true of a certain video file by calling UIVideoAtPathIsCompatibleWithSavedPhotosAlbum, and I saved the file by calling UISaveVideoAtPathToSavedPhotosAlbum.
The ALAssetsLibrary class extends these abilities by providing five additional methods:
writeImageToSavedPhotosAlbum:orientation:completionBlock:
Takes a CGImageRef and orientation.
writeImageToSavedPhotosAlbum:metadata:completionBlock:
Takes a CGImageRef and optional metadata dictionary (such as might arrive
through the UIImagePickerControllerMediaMetadata key when the user takes a
picture using UIImagePickerController).
writeImageDataToSavedPhotosAlbum:metadata:completionBlock:
Takes raw image data (NSData) and optional metadata.
videoAtPathIsCompatibleWithSavedPhotosAlbum:
Takes a file path string. Returns a boolean.
writeVideoAtPathToSavedPhotosAlbum:completionBlock:
Takes a file path string.
Saving takes time, so a completion block allows you to be notified when it’s over. The
completion block supplies two parameters: an NSURL and an NSError. If the first pa‐
rameter is not nil, the write succeeded, and this is the URL of the resulting ALAsset. If
the first parameter is nil, the write failed, and the second parameter describes the error.
700
| Chapter 17: Photo Library and Image Capture
www.it-ebooks.info
You can create in the Camera Roll / Saved Photos album an image or video that is
considered to be a modified version of an existing image or video, by calling an instance
method on the original asset:
• writeModifiedImageDataToSavedPhotosAlbum:metadata:completionBlock:
• writeModifiedVideoAtPathToSavedPhotosAlbum:completionBlock:
Afterward, you can get from the modified asset to the original asset through the former’s
originalAsset property.
You are also allowed to “edit” an asset — that is, you can replace an image or video in
the library with a different image or video — but only if your application created the
asset. Check the asset’s editable property; if it is YES, you can call either of these
methods:
• setImageData:metadata:completionBlock:
• setVideoAtPath:completionBlock:
Finally, you are allowed to create an album:
• addAssetsGroupAlbumWithName:resultBlock:failureBlock:
If an album is editable, which would be because you created it, you can add an existing
asset to it by calling addAsset:. This is not the same thing as saving a new asset to an
album other than the Camera Roll / Saved Photos album; you can’t do that, but once an
asset exists, it can belong to more than one album.
Using the Camera
The simplest way to prompt the user to take a photo or video is to use our old friend
UIImagePickerController, which provides an interface similar to the Camera app.
To use UIImagePickerController in this way, first check isSourceTypeAvailable: for
UIImagePickerControllerSourceTypeCamera; it will be NO if the user’s device has no
camera or the camera is unavailable. If it is YES, call availableMediaTypesForSourceType: to learn whether the user can take a still photo (kUTTypeImage), a video (kUTTypeMovie), or both. Now instantiate UIImagePickerController, set its source type to
UIImagePickerControllerSourceTypeCamera, and set its mediaTypes in accordance
with which types you just learned are available. Finally, set a delegate (adopting
UINavigationControllerDelegate and UIImagePickerControllerDelegate), and present
the view controller. In this situation, it is legal (and preferable) to use a presented view
controller even on the iPad.
So, for example:
Using the Camera
www.it-ebooks.info
|
701
BOOL ok = [UIImagePickerController isSourceTypeAvailable:
UIImagePickerControllerSourceTypeCamera];
if (!ok) {
NSLog(@"no camera");
return;
}
NSArray* arr = [UIImagePickerController availableMediaTypesForSourceType:
UIImagePickerControllerSourceTypeCamera];
if ([arr indexOfObject:(NSString*)kUTTypeImage] == NSNotFound) {
NSLog(@"no stills");
return;
}
UIImagePickerController* picker = [UIImagePickerController new];
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
picker.mediaTypes = @[(NSString*)kUTTypeImage];
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];
For video, you can also specify the videoQuality and videoMaximumDuration. More‐
over, these additional properties and class methods allow you to discover the camera
capabilities:
isCameraDeviceAvailable:
Checks to see whether the front or rear camera is available, using one of these values
as argument:
• UIImagePickerControllerCameraDeviceFront
• UIImagePickerControllerCameraDeviceRear
cameraDevice
Lets you learn and set which camera is being used.
availableCaptureModesForCameraDevice:
Checks whether the given camera can capture still images, video, or both. You
specify the front or rear camera; returns an NSArray of NSNumbers, from which
you can extract the integer value. Possible modes are:
• UIImagePickerControllerCameraCaptureModePhoto
• UIImagePickerControllerCameraCaptureModeVideo
cameraCaptureMode
Lets you learn and set the capture mode (still or video).
isFlashAvailableForCameraDevice:
Checks whether flash is available.
cameraFlashMode
Lets you learn and set the flash mode (or, for a movie, toggles the LED “torch”).
Your choices are:
702
|
Chapter 17: Photo Library and Image Capture
www.it-ebooks.info
• UIImagePickerControllerCameraFlashModeOff
• UIImagePickerControllerCameraFlashModeAuto
• UIImagePickerControllerCameraFlashModeOn
Setting camera-related properties such as cameraDevice when there is
no camera or when the UIImagePickerController is not set to cam‐
era mode can crash your app.
When the view controller’s view appears, the user will see the interface for taking a
picture, familiar from the Camera app, possibly including flash options, camera selec‐
tion button, digital zoom (if the hardware supports it), photo/video option (if your
mediaTypes setting allows both), and Cancel and Shutter buttons. If the user takes a
picture, the presented view offers an opportunity to use the picture or to retake it.
New in iOS 7, the first time your app tries to let the user capture video, a system dialog
will appear requesting access to the microphone. You can modify the body of this alert
by setting the “Privacy — Microphone Usage Description” key (NSMicrophoneUsageDescription) in your app’s Info.plist. See the discussion of privacy settings earlier in
this chapter. You can learn whether microphone permission has been granted by calling
the AVAudioSession method requestRecordPermission: (Chapter 14).
Allowing the user to edit the captured image or movie (allowsEditing), and handling
the outcome with the delegate messages, is the same as I described earlier. There won’t
be any UIImagePickerControllerReferenceURL key in the dictionary delivered to the
delegate, because the image isn’t in the photo library. A still image might report a UIImagePickerControllerMediaMetadata key containing the metadata for the photo. The
photo library was not involved in the process of media capture, so no user permission
to access the photo library is needed; of course, if you now propose to save the media
into the photo library (as I described in the previous section), you will need permission.
New in iOS 7, devices destined for some markets may request permis‐
sion to access the camera itself. But I have not been able to test this
feature.
Customizing the Image Capture Interface
You can customize the UIImagePickerController interface. If you need to do that, you
should probably consider dispensing with UIImagePickerController altogether and
designing your own image capture interface from scratch, based around AV Foundation
Customizing the Image Capture Interface
www.it-ebooks.info
|
703
and AVCaptureSession, which I’ll introduce in the next section. Still, it may be that a
modified UIImagePickerController is all you need.
In the image capture interface, you can hide the standard controls by setting showsCameraControls to NO, replacing them with your own overlay view, which you supply
as the value of the cameraOverlayView. In this case, you’re probably going to want some
means in your overlay view to allow the user to take a picture! You can do that through
these methods:
• takePicture
• startVideoCapture
• stopVideoCapture
The key to customizing the look and behavior of the image capture interface is that a
UIImagePickerController is a UINavigationController; the controls shown at the bot‐
tom of the default interface are the navigation controller’s toolbar. In this example, I’ll
remove all the default controls and use a gesture recognizer on the cameraOverlayView to permit the user to double-tap the image in order to take a picture:
// ... starts out as before ...
picker.delegate = self;
picker.showsCameraControls = NO;
CGRect f = self.view.window.bounds;
UIView* v = [[UIView alloc] initWithFrame:f];
UITapGestureRecognizer* t =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(tap:)];
t.numberOfTapsRequired = 2;
[v addGestureRecognizer:t];
picker.cameraOverlayView = v;
[self presentViewController:picker animated:YES completion:nil];
self.picker = picker;
Our tap: gesture recognizer action handler simply calls takePicture:
- (void) tap: (id) g {
[self.picker takePicture];
}
It would be nice, however, to tell the user to double-tap to take a picture; we also need
to give the user a way to dismiss the image capture interface. We could put a button and
a label into the cameraOverlayView, but here, I’ll take advantage of the UINavigation‐
Controller’s toolbar. We are the UIImagePickerController’s delegate, meaning that we
are not only its UIImagePickerControllerDelegate but also its UINavigationController‐
Delegate; I’ll use a delegate method to populate the toolbar:
704
|
Chapter 17: Photo Library and Image Capture
www.it-ebooks.info