Streaming is available in most browsers,
and in the Developer app.
-
Meet the new Photos picker
Let people select photos and videos to use in your app without requiring full Photo Library access. Discover how the PHPicker API for iOS and Mac Catalyst ensures privacy while providing your app the features you need. PHPicker is the modern replacement for UIImagePickerController. In addition to its privacy-focused approach, the API also provides additional features for your app like search, multi-image selection, and the ability to zoom in or out on on the photo grid. We'll show you how PHPicker can help most apps avoid asking for direct library access and how you can implement it to improve the overall experience for people interacting with your app.
Resources
Related Videos
WWDC23
WWDC22
WWDC21
WWDC20
-
Download
Hello and welcome to WWDC.
Hello my name is to be Tobias Conradi and I'm an engineer on the Photos team together with my colleague Justin. I will introduce you to the new photos picker. First of all let's answer this question. What is PHPicker. PHPicker is the system provided Picker which allows you to get access to photos and videos from the users photo library. It is now built-in support for search just like the photos app. It supports fluid zooming in the grid. And a very often requested feature, multiselect for third party apps, where you can even review your selection in one up. In short. PHPicker is the best way for an app to access photo and video data from photos. It comes in an all new design you just saw, has a new easy to use API, supports multi-selection and apps can specify the selectable types. Another very important feature is the privacy aspect of it. It has privacy built in by default. It doesn't need direct access to the photos library to show the picker and the picker won't prompt for access on behalf of the app. It provides the user selected photos and videos only which is a clear users intent of sharing the assets with your app. You might wonder how do we achieve that. Well PHPicker runs out of process of the app even though it looks like PHPicker runs inside the app. It is really running in a separate process from the host app rendered on top of it. But the app can't access the picker directly and not even take screenshots of the picker content. Only what the user actually selects is passed back to the host app. Now let's have a look at the different components of this API. First there is PHPicker conjuration which is used to specify the multi-select limit or with an optional PHPicker filter, we'd use the selectable types. The configuration is passed to PHPicker controller, doing its initialization. Set a delegate implement it in your app to get a response back. Once a user confirms the selection the picker creates an area of PHPicker result objects which is then passed back to the delegate. Let's have a look how it works in code. As I already mentioned you can use a configuration to specify the selection limit and filter down the visible item types. First we initialize the configuration.
You can specify a selection limit for the picker. The default limit is one and to allow unlimited selection, set the selection limit to zero. You can then specify a filter to restrict the types of selected items. For example if you only want to show images including live photos use the images filter. You can even combine photos. In this example the picker will only show videos and live photos but no images. The PHPickerViewController is initialized with the configuration. First let's create a configuration and then pass the configuration to the picker ViewController during initialization. Assign a delegate to the picker and present the picker. Please note that your app is responsible for presentation and dismissal of the picker. The picker doesn't manage presentation itself. Once the user confirms this selection the delegate method will be called. So first let's dismiss the picker. Then you can ask result object for an NSItemProvider. The itemProvider presents multiple different representations of the selected item. First ask the provider if it can load the type you want to load and then actually load it. Then your app can do something with the image. Remember that the item provider API is async and you should handle any errors occurring here. Let me now hand over to my colleague Justin to show it to you in Xcode. Thanks Tobias. Hello everyone. My name is Justin. I'm an engineer on the Photos team. I'm here to show you how you can integrate PHPicker into your iOS application. We will build a simple photo preview app that can display users likely photos on screen. Before we start let's spin it around to see what we have currently. We have a UIImageView displaying a placeholder image. There's also a plus button. but tapping it doesn't do anything right now. So what does it take to complete this app? We're going to do it in two steps. First we'll support previewing a single image. We'll present the picker when plus is tapped and display the selected image in the image view. Then we will add multi image support in addition to highlighting single selection. If more than one image is selected a user can display the next selected image by simply tapping on the screen. OK, now let's understand what features we want to add and start building. The first thing I need to do is to present the picker. I already set up the UI so presentPicker method will be called when plus is tapped. We just need to implement this method. To actually present the picker, we'll create the picker of configuration object first.
Since we're only interested in images we want to specify the images filter.
We don't need to set a selection limit because the default selection limit is one. We will add multi-selection support later. Then we can utilize PHPickers ViewController using the configuration object I just created. We want the presenting ViewController to receive user selections so we need to set delegate to self.
There seen just compiler error because our ViewController doesn't conform to the delegate protocol yet. Let's add this conformance to fix the error. We only need to implement a single method Picker didFinishPicking. It will give us an array of PHPicker objects. If a user cancels the picker that array will be empty. We should always dismiss the picker first.
Then we can check if we received any results. And if the itemProvider can load images we'll retrieve the image and display on screen. You should also handle the error when the image can't be retrieved. All right. That's all we need to implement single selection. Let's spin around to see what we have so far. Now if I tap the plus button the picker will be presented and if I select an image, it will be displayed on screen. It worked. It's that easy to use PHPicker API.
Great let's complete the app by adding multiple selection support. The first thing I need to do is to set selection limit to zero, to enable unlimited selection. The greatest thing about the new CIImage API is that we don't need to load all images upfront. We just need to keep a reference to all return item providers and load images when necessary. So let's do that.
Now we have a way to store all picker returned item providers. We also create iterator so can note which selected image is currently displayed. We can create a method to display the next image. You may notice that this is very similar to our previous single selection implementation. The only difference is we use the iterator to get the current item provider. Like I mentioned earlier whenever the screen is tapped we want to display the next image. So we just need to override touchHasEnded and call display next image. And lastly we can update our delegate method.
So we can delete single selection first.
We just need to save all item providers. Create an iterator and cut display next image to display the first image. That's all we need to implement Multi-image preview. Let's run the app to see what we've built.
I can tap the plus button.
You may notice that the picker now has a slightly different UI. That's because the picker knows we're in multi-selection mode. We'll also show the staging area and also the Add button on the top right corner.
I can select a couple images and tap Add.
The first image is quickly displayed on screen. Is it is as fast as single selection mode. Because we don't need to load other images at all.
And if I tap the screen the next image will be displayed. Tap it again and again and again until I reach the end.
That's it for a demo. Now back to Slides. I've seen the demo. You don't need to prep for photos library access to use PHPicker. Actually for most apps that only want photo and video data you don't need to use PhotoKit at all.
But at the same time, we know that some PhotoKit based apps want to get users PHAssets from the picture. So let's talk about how you can do that before without the API. Let's consider what types of apps can benefit from using PhotoKit. For example if your app supports Non-destructive image editing or has Photos Library organization features you need to use PhotoKit. But please make sure you only request photos library access when necessary and handle the case where photos library access is denied by the user. Let's look at some code now. Using PHPicker with PhotoKit is very simple. You just need to initialized PHPicker configuration with a PHPhotoLibrary object. Then you can get asset identifiers with a delegate callback and can fetch PHAssets using assetIdentifiers through standard PhotoKit API. In iOS 14 we also introduce a new feature called Limited Photos Library. If your app doesn't use PhotoKit you don't need to worry about that. For apps asking for photos library access we will show a third select photos option in the user prompt in addition to the existing allow access and no access options. The new option will only allow your app to access a portion of PHAssets in a users photo library. We believe it can enable users to better control their privacy. If a user places your app and your Limited Photos Library there's a couple things you need to keep in mind. First the picker will still show the entire photos library and all photos and videos can be selected by the user. Second no matter what user selects in the picker, PHAssets you can access will not change. If you want to request access to additional PHAssets there is a separate API for you to do that. For more information please refer to Handle the Limited Photos Library in Your App session. Now back to Tobias to wrap up this session. Thank you Justin. Let's talk about some best practices and summarize what you've just learned. First let's start with the deprecations reminder. AssetsLibrary has been deprecated years ago and is still deprecated. If your app needs access to the first library please switch over to PhotoKit. AssetsLibrary will eventually go away. The other deprecation announcement today is about the photos library portion of UIImagePickerController. It is being deprecated and we encourage you to adopt PHPickerViewController instead. If your app and its access to images or videos would really encourage you to adopt the new PHPicker instead of writing your own custom picker UI. It supports multi select search and has a consistent UI with the main Photos app. So your users already know how to get to the images and videos. Also please don't prompt for photo library access before showing the picker and don't require the user to grant you access before showing the picker. There's no need to do any of this. And it doesn't help with the users trust into your app. If your app leverages PhotoKit to access the photos library please reconsider if it really needs to have access to the library or if you can use PHPicker instead. We really hope you like the new picker and API. We're looking forward to you adopting it in your apps. Thank you.
-
-
2:27 - PHPickerConfiguration
import PhotosUI var configuration = PHPickerConfiguration() // “unlimited” selection by specifying 0, default is 1 configuration.selectionLimit = 0 // Only show images (including Live Photos) configuration.filter = .images // Uncomment next line for other example: Only show videos or Live Photos (for their video complement), but no images // configuration.filter = .any(of: [.videos, .livePhotos])
-
3:07 - PHPickerViewController
import UIKit import PhotosUI class SingleSelectionPickerViewController: UIViewController, PHPickerViewControllerDelegate { @IBAction func presentPicker(_ sender: Any) { var configuration = PHPickerConfiguration() // Only wants images configuration.filter = .images let picker = PHPickerViewController(configuration: configuration) picker.delegate = self // The client is responsible for presentation and dismissal present(picker, animated: true) } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { // The client is responsible for presentation and dismissal picker.dismiss(animated: true) // Get the first item provider from the results, the configuration only allowed one image to be selected let itemProvider = results.first?.itemProvider if let itemProvider = itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) { itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in // TODO: Do something with the image or handle the error } } else { // TODO: Handle empty results or item provider not being able load UIImage } } }
-
5:19 - Demo - Single Selection
import UIKit import PhotosUI class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! @IBAction func presentPicker(_ sender: Any) { var configuration = PHPickerConfiguration() configuration.filter = .images let picker = PHPickerViewController(configuration: configuration) picker.delegate = self present(picker, animated: true) } } extension ViewController: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { dismiss(animated: true) if let itemProvider = results.first?.itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) { let previousImage = imageView.image itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in DispatchQueue.main.async { guard let self = self, let image = image as? UIImage, self.imageView.image == previousImage else { return } self.imageView.image = image } } } } }
-
7:34 - Demo - Multiple Selection
import UIKit import PhotosUI class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! var itemProviders: [NSItemProvider] = [] var iterator: IndexingIterator<[NSItemProvider]>? @IBAction func presentPicker(_ sender: Any) { var configuration = PHPickerConfiguration() configuration.filter = .images configuration.selectionLimit = 0 let picker = PHPickerViewController(configuration: configuration) picker.delegate = self present(picker, animated: true) } func displayNextImage() { if let itemProvider = iterator?.next(), itemProvider.canLoadObject(ofClass: UIImage.self) { let previousImage = imageView.image itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in DispatchQueue.main.async { guard let self = self, let image = image as? UIImage, self.imageView.image == previousImage else { return } self.imageView.image = image } } } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { displayNextImage() } } extension ViewController: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { dismiss(animated: true) itemProviders = results.map(\.itemProvider) iterator = itemProviders.makeIterator() displayNextImage() } }
-
11:13 - Using PHPicker with PhotoKit
import UIKit import PhotosUI class PhotoKitPickerViewController: UIViewController, PHPickerViewControllerDelegate { @IBAction func presentPicker(_ sender: Any) { let photoLibrary = PHPhotoLibrary.shared() let configuration = PHPickerConfiguration(photoLibrary: photoLibrary) let picker = PHPickerViewController(configuration: configuration) picker.delegate = self present(picker, animated: true) } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) let identifiers = results.compactMap(\.assetIdentifier) let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: nil) // TODO: Do something with the fetch result if you have Photos Library access } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.