Picking Photos With The PhotosPicker View In SwiftUI

Posted in SwiftUI

May 1st, 2023

⏱ Reading Time: 8 mins

Back in 2020 Apple presented a new photos picker API to developers known as the PHPicker API. PHPicker lets users get access to their photos in a safe manner through third-party apps, as the picker runs in a different process and does not require full access to Photos library. That API has to offer some great features and capabilities, however its disadvantage lies to the fact that it was not designed to work natively in SwiftUI. It’s a perfect fit for UIKit, but a UIViewControllerRepresentable type has to be implemented mandatorily as the middle man between PHPicker API and SwiftUI.

That changed in WWDC 2022 though, when Apple finally introduced PhotosPicker; a SwiftUI native view that provides the full range of functionalities that PHPicker API does -its counterpart actually- but lifting all the unavoidable complexities and hassle required until that point.

PhotosPicker functionality is based on another new API presented in WWDC 2022, the Transferable protocol. Fetching photos, videos, or other assets, such as live photos or bursts takes just a few lines of code thanks to that, something that becomes even faster when combined with the new async/await APIs. Overall, using PhotosPicker is a simple task for the majority of use cases. In this post you’ll meet how to present PhotosPicker in SwiftUI, and how to load items from the photo library once users have finished selecting them.

Presenting the PhotosPicker view

Usually, most views are part of the SwiftUI framework, however that’s not the case with photos picker. To make it available and start using it, import the PhotosUI framework:

At its simplest form, the following code demonstrates what we need in order to trigger the appearance of the PhotosPicker view in SwiftUI:

The first argument is the binding value of either a state or published property that will store the selected item by the user:

PhotosPickerItem, the type of the selectedItem property, is a type that conforms to Transferable protocol, and that will help us get the selected item easily in a while.

The second argument in the PhotosPicker view is a label; this can be any SwiftUI content you desire, exactly just like the label of a button. In fact, PhotosPicker is a button with a predefined action; to present the actual photos picker when pressed. To keep things simple here, a Label view with some text and a SF Symbol image is just enough.

Obviously, we can treat PhotosPicker like any other button and style it with view modifiers as necessary. For instance, we can update the default button style and its foreground color:

When the PhotosPicker view gets pressed the actual picker will show up. There, and depending on how we configure the picker, users can select either one or several photos or other assets they want and add them to the app.

Filtering the displayed content

There are more arguments that the PhotosPicker initializer can get, with the most common being a filter regarding the kind of assets the picker is going to display. For example, the value supplied to the matching argument next will make the picker display only images and nothing else:

images is a static property in a struct called PHPickerFilter, and it’s just one among several properties that PHPickerFilter contains. Have a look at the official documentation page to see them all.

It’s interesting that besides single values, we can also combine filters in order to let the picker show different kind of media. For instance, the following will make the picker display all videos and live photos found in the device’s library. Notice that in this case filters are elements of an array, and that array is an argument to the any(of:) method:

Providing additional arguments

When making apps that let users import photos or other media from the photo library, then it might be useful and meaningful to limit the number of items that can be selected. You might want to allow picking just one photo, or up to two videos, or an arbitrary number of live photos. No matter what the occasion might be, PhotosPicker view makes it really straightforward to set a limit to the number of selected items:

The code you see here allows users to select up to five photos when the picker will show up. To keep the default settings, simply avoid providing any value to the maxSelectionCount argument.

???? Important Note: You have probably noticed that in the previous code the binding value is named selectedItems instead of selectedItem, indicating the use of an array. Indeed, and you can call this a rule, the given binding value must be an array of PhotosPickerItem objects ([PhotosPickerItem]) mandatorily when you need to let users select more than one items. Otherwise, the first selected item will make the picker disappear without giving the possibility to pick additional items. I’m covering the case of selecting multiple items later on in this post.

It might be also meaningful to keep the order users select photos or other items sometimes. To manage that, give the value .ordered to the selectionBehavior argument:

In this case, selected items will show badges that indicate the order of selection instead of badges with a checkmark.

Lastly, there is one more argument that we can supply PhotosPicker view initializer with, called preferredItemEncoding. It’s recommended by the documentation to leave its value to current and avoid transcoding of images and videos.

Most of the times you won’t need to even deal with that argument, so just skip it. As a final note, all arguments presented so far can be put together in the same initializer so PhotosPicker can be configured any way we like it.

Fetching the selected item

Having seen how to configure PhotosPicker, it’s now time to find out how to get the selected items once the user is done with the picker. In most of the previous examples, there is the selectedItem binding value passed as argument, and this is where we’ll get the selected item from.

We want to fetch the actual item once the value of selectedItem has changed, so we need to observe for changes taking place in it. In SwiftUI we do that using the onChange(of:perform:) view modifier. You may read more about it here.

The newItem parameter value of the closure contains the selected item by the user, and similarly to selectedItem, it is also a PhotosPickerItem value. The latter conforms to Transferable protocol, which in turn provides us with a couple of methods to get the transferred items; in this case the selected media.

The easiest way to fetch whatever the user has selected is by calling an asynchronous method (async) named loadTransferable(type:), accessible through the newItem object. When invoking this method it’s mandatory to prepend it with the await keyword. On top of that, loadTransferable(type:) is a throwing method, so we also need to either use a try-catch statement, or optionally unwrap its return value using the try? keyword.

To keep things as simple as possible we’ll avoid using a do-catch here, but we’ll unwrap everything using a guard let statement like so instead:

Note a few things here:

  1. It’s mandatory to include everything in a Task since we’re performing an asynchronous operation.
  2. There is no guarantee that newItem will always have a value; it can be nil, so we treat it as an optional.
  3. The argument in the loadTransferable(type:) method is the type that we need to convert transferred data to. Here, a Data object.

If everything goes normally and no errors occur along the way, then the imageData will contain the data of the selected item. Supposing that we’re fetching images, then we can initialize a UIImage object with that data like so:

The above constitutes the fastest and easiest way to fetch the selected item from the picker. The selectedPhoto property used here is another state property (it can also be a @Published property in a view model) that stores the fetched UIImage:

We can now put everything together, fetch an image, and then display it using an Image view:

Getting an Image instead of a UIImage

Just right above I demonstrated how to get a UIImage object from the selected image in the picker. You might find doing so unnecessary, and consider getting a SwiftUI Image object directly as a more useful approach. You can definitely change what I showed and do exactly that:

Here, the selectedImage is the following:

selectedImage will either get an actual value if unwrapping everything won’t fail and not any errors will occur, or it will remain nil otherwise. To display it, simply modify the sample code shown in the previous part like so:

However, there is an important downside to note here. According to the documentation, getting an Image instance from the loadTransferable(type:) method works only for png images. The code illustrated here won’t work for other type of photos, such as jpeg or gif.

If that’s prohibiting to you, then getting the selected item as a UIImage is an one-way road. If your purpose is to let users fetch png images only, then getting an Image object as shown in this part will work fine.

Fetching multiple items

There are a few required changes in the code you’ve seen so far when it’s necessary to let users choose multiple elements in the photos picker. The starting and pretty important point is the binding value we provide the PhotosPicker initializer with; it has to be an array of PhotoPickerItem objects:

In this case, selectedItems is declared like so:

Without any other configuration, users can pick as many photos as they want. Apart from the above, however, the way we fetch selected items must be adapted as well.

Using the onChange(of:perform:) modifier once again, what we have to do is pretty much the same to what it’s demonstrated in previous parts. But with one difference; we begin to work with arrays and not single objects:

newItems is an array and we’re running through its elements using the forEach(_:) higher order function. For every single item contained in the item parameter value of the closure, we’ll perform actions that we’ve talked about already:

  • We’ll create a new Task in order to fetch each image asynchronously.
  • Using the loadTranferable(type:) we’ll get the data of each image.
  • We’ll convert that data to an UIImage object.

There is though an addition as well. That is, every resulting UIImage object will be appended into an array. All these are shown in the following code segment:

selectedPhotos is an array of UIImage objects, declared as a @State property like this:

Once again, let’s put everything together in order to demonstrate how multiple items can be fetched using the photos picker:

Conclusion

Undeniably photos picker had been a missing native element in SwiftUI. However it comes with a downside, which is no support for older system versions. PhotosPicker API is available in iOS 16 and above, and macOS 13 and above. For previous systems, it’s still necessary to resort to UIViewControllerRepresentable implementations and use PHPicker APIs.

What I’ve shown in this post is how to configure photos picker, and how to fetch single or multiple selected items using the fastest and easiest approach. But that’s not all, as there are still topics not covered here, such as how to get selected items as files, how to manage loading progress, how to deal with thumbnails, and more. All these may be the subject of future posts. Until then, though, thanks for reading and take care! ????

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.