How to select images using PHPickerViewController with SwiftUI

Jan 9, 2021

Selecting images from our iPhone library is needed when changing a profile picture, posting an update, or sharing the photo of your pet. In this post, we are going to look into how to use PHPickerViewController with SwiftUI. Apple announced this view controller at WWDC2020.

What is PHPickerViewController?

PHPickerViewController is a view controller that gives way for our app users to pick assets from their photo library. It provides a well-known user interface, and we don’t need to bother about building that.

One nicety that comes with this approach is that we don’t need to worry about adding information to access our user’s photo library in Info.plist file. Users can decide to allow access either to all photo library or selectively grant access to particular photos. That solves privacy concerns where apps could read the whole library and even track people.

Creating a PHPickerViewController

To create a PHPickerViewController we need to initialize it by passing configuration, which is an instance of PHPickerConfiguration. For the configuration, we need to specify what type of pictures we want and set the selection limit.

For the PHPickerViewController itself, we set the delegate that needs to implement PHPickerViewControllerDelegate protocol. It has just one method that gives a signal when our user has selected photos.

Integrate with SwiftUI

To use the PHPickerViewController in SwiftUI apps, we need to use the UIViewControllerRepresentable to represent an UIKit view controller. Let’s go over all the steps required in doing that.

Setting up the UIKit view controller

UIViewControllerRepresentable is a protocol and requires to implement two methods:

  • makeUIViewController - create and configure the view controller;
  • updateUIViewControoler - update the state of the view controller.

We are going to create and configure the PHPickerViewController in the makeUIViewController method. We don’t need to update it, so we can leave the updateUIViewControoler empty.

struct PhotoPicker: UIViewControllerRepresentable {
  @Binding var pickerResult: [UIImage] // pass images back to the SwiftUI view
  @Binding var isPresented: Bool // close the modal view
  
  func makeUIViewController(context: Context) -> some UIViewController {
    var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
    configuration.filter = .images // filter only to images
    configuration.selectionLimit = 0 // ignore limit
    
    let photoPickerViewController = PHPickerViewController(configuration: configuration)
    photoPickerViewController.delegate = context.coordinator // Use Coordinator for delegation
    return photoPickerViewController
  }
  
  func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
}

Coordinator

Now we can present the PHPickerViewController, but how can we transfer back the selected images? We need to use the makeCoordinator method and create the Coordinator class that implements the PHPickerViewControllerDelegate protocol. It is a thoughtful approach to how the SwiftUI can communicate with the UIKit delegation pattern idea.

struct PhotoPicker: UIViewControllerRepresentable {

  // ...

  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
  
  // Create the Coordinator, in this case it is a way to communicate with the PHPickerViewController
  class Coordinator: PHPickerViewControllerDelegate {
    private let parent: PhotoPicker
    
    init(_ parent: PhotoPicker) {
      self.parent = parent
    }
    
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    }
  }
}

Using UIKit with SwiftUI

Let’s put it all together. We need to create a SwiftUI view to present the photo picker view and present it as a modal. You can read more about how to display a modal view in SwiftUI in my previous post.

.sheet(isPresented: $photoPickerIsPresented) {
  // Present the photo picker view modally
  PhotoPicker(pickerResult: $pickerResult,
              isPresented: $photoPickerIsPresented)
}

Passing data from UIKit to SwiftUI

To get photos of what users have selected, let’s use @State variable and pass it to the PhotoPicker view using @Binding property wrapper.

Now we can fully complete the PHPickerViewControllerDelegate protocol for our Coordinator class.

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
  parent.pickerResult.removeAll() // remove previous pictures from the main view
  
  // unpack the selected items
  for image in results {
    if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
      image.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] newImage, error in
        if let error = error {
          print("Can't load image \(error.localizedDescription)")
        } else if let image = newImage as? UIImage {
          // Add new image and pass it back to the main view
          self?.parent.pickerResult.append(image)
        }
      }
    } else {
      print("Can't load asset")
    }
  }
  
  // close the modal view
  parent.isPresented = false
}

What we are doing here is unpacking selected items from the photo library and set them to the parent’s @Binding variable. By doing so, we are transferring the data back to the main view.

Present the selected images

To present the picked photos, we can iterate over and show them in a ScrollView.

ScrollView {
  ForEach(pickerResult, id: \.self) { uiImage in
    ImageView(uiImage: uiImage)
  }
}

An important fact is that the type of image is UIImage, but luckily SwiftUI provides a nice initializer for the Image view passing the UIImage type.

You can check out the full implementation here.

TL;DR

Selecting images and using them in our apps is an essential feature in modern iOS applications. Apple announced in WWDC2020 a new way to do it in a more secure and granular way - PHPickerViewController. Note that it is available only from iOS14 and later versions.

To use the PHPickerViewController with SwiftUI, we need to implement the UIViewControllerRepresentable protocol. It allows communicating from and to UIKit view controllers flawlessly.

Links