PhotosPicker on SwiftUI
Use Apple's photo picker across iOS, iPadOS, macOS, and watchOS with SwiftUI and Transferable
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.
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.
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
- Use
PhotosPicker
view to obtain one or more instances ofPhotosPickerItem
- 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.
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.