1. Trang chủ >
  2. Công Nghệ Thông Tin >
  3. Kỹ thuật lập trình >

Chapter 17. Photo Library and Image Capture

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



Xem Thêm
Tải bản đầy đủ (.pdf) (929 trang)

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×