Brian’s Brain

I Need a Tagline

Pholio Coder's Guide: Picking Pictures

The most basic thing a user needs to do with a photo gallery application is pick photos for a gallery. If you’re learning iPhone / iOS programming, you’ll first discover the UIImagePickerController class and think your job is done.

Nope. UIImagePickerController is fantastic for simple applications, but it has one dealbreaker for a photo gallery application: It only lets you pick one image at a time. If you are trying to create a gallery of multiple images, this becomes tedious fast. Nobody will want to keep using your program.

There is a better way: Use the Assets Library APIs directly ALAssetsLibrary. The Assets Library gives you fine-grained control over accessing all media that is available in the Photos application. It has only two drawbacks.

  1. You have to write more code. A challenge, but not insurmountable.

  2. The first time your program calls an ALAssetsLibrary API directly, iOS prompts the user to inform him that your program wants to access location data. If the user does not consent, you don’t get access to pictures.

    The reason for the prompt: Photos you take with the iPhone camera get geotagged. By reading the photos on the iPhone, you can make reasonable inferences about where the user has been. UIImagePickerController avoids the prompt because it strips out all location metadata from the image before returning it to you. When you access ALAssetsLibrary directly, however, you can read the location information. Thus the prompt. There’s no way around it. If you want to support multiple image picking in an iPhone / iPad application, you need to get the user’s consent for access to location information.

If you’re trying to decide if you should forego the convenience of UIImagePickerController and start tapping into the power of ALAssetsLibrary, the second drawback is the one you should focus on. Users will likely find the “Location” prompt scary, especially if they’re not expecting it. You’ll probably lose some people. But on the flip side, there’s no other way to let your users pick multiple images. Is it worth it?

If it is, get ready to buckle down and write some code. If you’re lazy, you could copy the image picking code from Pholio. It’s about a dozen classes. Better yet, you could copy ELCImagePickerController from iCode blog, which is really a fantastic piece of work. ELCImagePickerController inspired Pholio’s code, and I do use some of its algorithms & images. If all you need is a way to pick multiple images from the iPad / iPhone galleries, then you should just surf over to iCode blog and be done.

Pholio has an additional design goal, however. In addition to choosing photos from the iPad library, I wanted to give users the power to select photos from web sites, like Flickr. I set out to design a single image picker that could handle multiple image sources. If your program needs capabilities like this, then you’re going to be better off if you just design your own image picker to meet your own needs. It’s more typing than just using UIImagePickerController or copying ELCImagePickerController, but it’s not that hard. The rest of this article walks through the important design decisions for your picker and how they were addressed in Pholio.

Invoking the Picker

It sounds silly, but the first decision you should make is how you want to invoke your picker from your program. This decision creates the scaffolding for the rest of your picker work. For Pholio, I decided I wanted one picker class (creatively called BDImagePickerController) that exposes exactly one class method:

1
2
3
4
5
6
7
8
9
  //
  //  Block type that will get invoked when the user picks images.
  //

  typedef void (^BDImagePickerControllerImageBlock)(NSArray *images);

  + (UIPopoverController *)presentPopoverFromRect:(CGRect)rect
                                           inView:(UIView *)view
                                      onSelection:(BDImagePickerControllerImageBlock)imageBlock;

I think this is the simplest possible way to interact with an image picker. It forces the image picker to appear in a UIPopoverController (which is fine as long as you target the iPad). You get the UIPopoverController in case you need to dismiss it yourself. (Remember, the iPad Human Interface Guidelines say you must have no more than one popover controller visible at a time. If you want to show a new one, dismiss the old one!) Then, if the user selects images, the picker will call imageBlock and give it the array of selected images. No delegate protocol to conform to, no delegate property to forget to set; everything can be written simply and inline like this (from IPPortfolioGridViewController.m):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  ////////////////////////////////////////////////////////////////////////////////
  //
  //  Insert photos into an existing set.
  //

  - (void)gridView:(BDGridView *)gridView didInsertIntoCell :(BDGridCell *)cell {

    IPSetCell *setCell = (IPSetCell *)cell;
    self.popoverController = [BDImagePickerController presentPopoverFromRect:cell.frame
                                                                      inView:gridView
                                                                 onSelection:
                              ^(NSArray *assets) {

                                //
                                //  PROCESS ASSETS HERE
                                //
                              }];
  }

Getting the Image Data

The image controller has called your block, and it’s given you an array of assets that the user has picked. Now what? How do you get the image data into the rest of your program – made more complicated if some assets need to get downloaded from a site like Flickr?

In Pholio, I addressed this problem by making each object in the assets array (above) conform to the BDSelectableAsset protocol. The protocol defines a few methods, but the important one right now is:

1
  - (void)imageAsyncWithCompletion:(void(^)(NSString *filename, NSString *uti))completion;

This method codifies the next two design principles of Pholio’s image picker:

  1. Getting images can take a long time, so don’t get them synchronously. Instead, get them asynchronously and provide a block to call when the image is ready.

  2. Uncompressed images can be big. So instead of returning uncompressed image data, write the image to a file in the application documents folder (compressed as JPEG or PNG) and give the file name and type to the completion block.

So the caller of the image picker has a pretty easy job: Use the single class method of BDImagePickerController to present the picker, then in the completion block get the image file using imageAsyncWithCompletion. But how does the picker itself work?

Supporting multiple image sources

The way Pholio’s picker supports multiple image sources is by defining two roles: An asset source and an asset. An asset source simply knows how to create an array of interesting assets that the user might want to choose from. An asset knows how to produce thumbnails and image files on demand. Assets also remember whether or not they have been selected. These two roles are made concrete in two Objective-C protocols: BDAssetsSource and BDSelectableAsset, respectively.

With these two protocols defined, I wrote one concrete class, BDAssetsGroupController, that knows how to get an array of assets from a BDAssetsSource, display them, let the user select and unselect assets, and finally return the results to the caller. Then I wrote implementations of those protocols for the Assets Library (BDALAssetsGroupSource and BDSelectableALAsset) and Flickr (IPFlickrSearchSource and IPFlickrSelectableAsset).

Asset classes

Putting it all together

At this point, you understand all of the building blocks of Pholio’s image picker. Putting them together is easy. Inside BDImagePickerController, I create one table view that knows how to enumerate Assets Library groups (BDAssetsLibraryController) and one table view that knows how to enumerate Flickr photosets (IPFlickrSetPickerController). These controllers get wrapped up inside UINavigationController and a UITabBarController. They push BDAssetGroupController views on their navigation stacks.

Easy as pie!

As I said at the outset, if you want to support multiple image selection, be prepared to write code. But I hope this (and the corresponding source code) demystify the process of creating a custom image picker that supports multiple selection and supports multiple image sources.