Using PHPickerViewController to Select a Photo on iOS 14

How to use the new PHPickerViewController class in iOS 14 to select a photo from the Photo Library on iOS in Objective C and Swift

For many years the simplest way to selection photos and videos on iOS has been to use the UIImagePickerController class. The class allowed you to present a built in system UI to select a photo or video and return it to your app, without having to built the selection UI or prompt for access to the photo library.

The code to do so was incredibly simple:

// Objective C
- (void)pickPhoto
{
    UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
    imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    imagePicker.delegate = self;
    [self presentViewController:imagePicker animated:YES completion:nil];
}

// Implement UIImagePickerControllerDelegate method
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info
{
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    self.imageView.image = image;

    [picker dismissViewControllerAnimated:YES completion:nil];
}
// Swift
func pickPhoto()
{
    let imagePicker = UIImagePickerController()
    imagePicker.sourceType = .photoLibrary
    imagePicker.delegate = self
    present(imagePicker, animated: true)
}

// Implement UIImagePickerControllerDelegate method
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any])
{
    imageView.image = (info[.originalImage] as? UIImage)
    picker.dismiss(animated: true, completion: nil)
}

However, UIImagePickerController had a number of disadvantages: it was fairly basic and limited in the UI it gave the users to browse their library with; it only allowed selection of one item at a time, and had only basic filtering support. Now in iOS 14 the UIImagePickerController is "soft deprecated". Although not currently marked as deprecated, if you look at the header file you'll see the API marker with this:

API_DEPRECATED("Will be removed in a future release, use PHPicker.", ios(11, API_TO_BE_DEPRECATED));

API_TO_BE_DEPRECATED is defined with this comment above it.

/* API_TO_BE_DEPRECATED is used as a version number in API that will be deprecated 
 * in an upcoming release. This soft deprecation is an intermediate step before formal 
 * deprecation to notify developers about the API before compiler warnings are generated.
...

Instead iOS 14 now includes the PHPickerViewController with support for multiple selection and a much improved UI.

PHPicker

Rather than being in UIKit, the new PHPicker. classes in iOS 14 are located in the PhotosUI framework and include:

You present a PHPickerViewController, which has a PHPickerConfiguration to tell it how many items to select, and of what type. The types of items allowed are defined by the PHPickerConfiguration's filter property, which has the possible option of being any combination of: images, live photos, or videos. How many items users can select is controlled from the PHPickerConfiguration's selectionLimit property.

PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init];
config.selectionLimit = 3;
config.filter = [PHPickerFilter imagesFilter];

PHPickerViewController *pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config];
pickerViewController.delegate = self;
[self presentViewController:pickerViewController animated:YES completion:nil];

PHPickerConfiguration, PHPickerFilter, and PHPickerResult all bridge across into Swift as Structs, not as classes.

var config = PHPickerConfiguration()
config.selectionLimit = 3
config.filter = PHPickerFilter.images

let pickerViewController = PHPickerViewController(configuration: config)
pickerViewController.delegate = self
self.present(pickerViewController, animated: true, completion: nil)

The results from PHPickerViewController are returned differently than UIImagePickerController, instead using NSItemProvider, like the drag and drop APIs.

NOTE: There is an assetIdentifier property defined on PHPickerResult, which is documented as Local identifier of the selected asset. However, (as of iOS 14.0 beta 1, in my testing this is always nil; regardless of whether or not the app has been granted full photo library access.
I presume that is meant to be the PHAsset's assetIdentifier allowing us to retrieve the asset object with a call to asset = PHAsset.fetchAssets(withLocalIdentifiers: [photoID], options: nil).firstObject.

// Objective C

-(void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results{
   [picker dismissViewControllerAnimated:YES completion:nil];
    
   for (PHPickerResult *result in results)
   {
      // Get UIImage
      [result.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id<NSItemProviderReading>  _Nullable object, NSError * _Nullable error)
      {
         if ([object isKindOfClass:[UIImage class]])
         {
            dispatch_async(dispatch_get_main_queue(), ^{
               NSLog(@"Selected image: %@", (UIImage*)object);
            });
         }
      }];
   }
}
// Swift
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
   picker.dismiss(animated: true, completion: nil)
   
   for result in results {
      result.itemProvider.loadObject(ofClass: UIImage.self, completionHandler: { (object, error) in
         if let image = object as? UIImage {
            DispatchQueue.main.async {
               // Use UIImage
               print("Selected image: \(image)")
            }
         }
      })
   }
}

Filtering and Multi-selection

By default the selectionLimit is set to 1, and there is no filter.

The change what is selected, and how many, we just need to change the PHPickerConfiguration.
For example: to select up to 10 videos we would need to specify.

PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init];
config.selectionLimit = 10;
config.filter = [PHPickerFilter videosFilter];

PHPickerConfiguration, PHPickerFilter, and PHPickerResult all bridge across into Swift as Structs, not as classes.

var config = PHPickerConfiguration()
config.selectionLimit = 10
config.filter = PHPickerFilter.videos

Setting the selection limit to 0 allows selecting as many items as the system supports.

Example Projects

The 4 screens of the example Xcode project

The example code Xcode Projects demonstrating how to use the new API are available on Github: