PhotosPicker on SwiftUI

PhotosPicker on SwiftUI

Use Apple's photo picker across iOS, iPadOS, macOS, and watchOS with SwiftUI and Transferable

Β·

3 min read

In this blog post I'll show you how to use the new SwiftUI PhotosPicker API that was announced in the WWDC22 session What's new in the Photos picker.

Picker API available on SwiftUI

The new SwiftUI PhotosPicker API is available on all picker-supported platforms:

  • iOS (16.0+)
  • iPadOS (16.0+)
  • macOS (13.0+)
  • watchOS (9.0+)

You no longer have to use UIViewControllerRepresentable to wrap either PHPickerViewController or its older version UIImagePickerController.

The picker runs out of process, so your app doesn't need to request any library access to use it.

WWDC22 Presentation SwiftUI Picker multiSelection

The picker will automatically choose the best layout depending on the platform, your app's configuration, and available screen space. You don't need to worry about what the picker UI should be, so you just can focus on making your app better.

From a developer's perspective, there are two steps

  1. Use PhotosPicker view to obtain one or more instances of PhotosPickerItem
  2. Load the asset data on demand with PhotosPickerItem.loadTransferable

Use PhotosPicker view to obtain one or more instances of PhotosPickerItem

There are various initializers for PhotosPicker ,so I encourage you to read through the documentation.

Here is an example of using PhotosPicker to select a single image by clicking on an image.

Example

Load the asset data on demand with PhotosPickerItem.loadTransferable

Please note that asset data won't be loaded immediately. The load operation could also fail if an error was encountered, for example, when the picker was trying to download data from iCloud Photos but the device was not connected to the internet.

The PhotosPicker uses Transferable, which is a new SwiftUI protocol for transferring data between apps and extensions.

During Apple's demo it is suggested that you can specify the type as Image and that loadTransferable(type:) will decode and return a respective Image instance.

private func loadTransferable(from imageSelection: PhotosPickerItem) -> Progress {
    return imageSelection.loadTransferable(type: Image.self) { result in
        DispatchQueue.main.async {
            guard imageSelection == self.imageSelection else { return }
            switch result {
            case .success(let image?):
                self.imageState = .success(image)
            case .success(.none):
                self.imageState = .empty // <= during my testing I always ended up here
            case .failure(let error):
                self.imageState = .failure(error)
            }
        }
    }
}

However this was not working for me. Maybe it's a bug in the beta version. What worked for me was to specify the type as Data, use its data to create a UIImage instance and finally create an Image instance.

private func loadTransferable(from imageSelection: PhotosPickerItem) -> Progress {
    return imageSelection.loadTransferable(type: Data.self) { result in
        DispatchQueue.main.async {
            guard imageSelection == self.imageSelection else { return }
            switch result {
            case let .success(data?):
                guard let uiImage = UIImage(data: data) else {
                    self.imageState = .empty
                    return
                }
                self.imageState = .success(Image(uiImage: uiImage))
            case .success(.none):
                self.imageState = .empty
            case let .failure(error):
                self.imageState = .failure(error)
            }
        }
    }
}

Apple has not released the code used from the WWDC22 session to demo PhotosPicker. I created essentially the same code and put it up on GitHub as a public gist.

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!