SwiftUI · · 4 min read

How to Use PhotosPicker in SwiftUI

How to Use PhotosPicker in SwiftUI

Prior to iOS 16, if you need to display a photo picker for users to choose photos from Photo library, you have to rely on PHPickerViewController or the older UIImagePickerController of UIKit. It’s not difficult to use it as you can integrate UIKit components with UIViewControllerRepresentable. That said, it would be great if the SwiftUI framework comes with a native view for photo picker.

In iOS 16, Apple finally brings PhotosPicker to SwiftUI that it has the same functionalities as its UIKit counterpart. If your app will only support device running iOS 16 or up, you can use this new view for handling photo selections.

Let’s see how it works with some sample code. Please note that you need to use Xcode 14 beta 4 to follow this tutorial.

Using PhotosPicker in SwiftUI

The PhotosPicker view is bundled in the PhotosUI framework. Before using it, you have to first import the framework:

import PhotosUI

Next, we declare a state variable to hold the selected photo:

@State private var selectedItem: PhotosPickerItem?

It’s pretty easy to bring up the photos picker. Here is the basic usage of PhotosPicker:

PhotosPicker(selection: $selectedItem, matching: .images)) {
    Label("Select a photo", systemImage: "photo")
}
.tint(.purple)
.controlSize(.large)
.buttonStyle(.borderedProminent)

You instantiate PhotosPicker by passing it a binding to the selected item and a photo filter. In the closure, you describe the appearance of the button. With a few lines of code, Xcode should show you a button in the preview.

swiftui-photospicker-button

If you click the button, it displays a Photos picker for choosing images from the photo library. When you choose a photo, the photo picker automatically dismisses and the selected photo item is stored in the selectedItem variable.

swiftui-photospicker-demo

Filtering the Photos

The matching parameter lets you specify the photo filter to apply to the photo library. In the code above, we set its value to .images to show images only. If you want to display both images and videos, set the value of the parameter to the following:

.any(of: [.images, .videos])

The .images filter includes all images in the user’s photo library. What if you want to exclude live photos from the image set? You can set the value like this:

.any(of: [.images, .not(.livePhotos)])

You use the .not filter to exclude Live Photos.

Handling the Photo Selection

As mentioned earlier, the selected photo is automatically stored in the selectedItem variable, which has a type of PhotoPickerItem. So, how can we load the photo and display it on screen?

First, we attach the onChange modifier to listen to the update of the selectedItem variable. Whenever there is a change, we call the loadTransferable method to load the asset data.

.onChange(of: selectedItem) { newItem in
    Task {
        if let data = try? await newItem?.loadTransferable(type: Data.self) {
            selectedPhotoData = data
        }
    }
}

In the WWDC22 session (What’s new in the Photos picker), Apple’s engineer showed us to specify the type as Image.self. This is to instruct loadTransferable to return an instance of Image. However, I couldn’t make it work on Xcode 14 beta 4. This is why I used Data.self instead. Later, we can convert the data into an UIImage object for displaying in an Image view.

The selectedPhotoData variable is another state variable that is used to hold the data object:

@State private var selectedPhotoData: Data?

To display the selected image in an image view, we create an instance of UIImage using the image data and then pass it to the Image view:

if let selectedPhotoData,
    let image = UIImage(data: selectedPhotoData) {

    Image(uiImage: image)
        .resizable()
        .scaledToFill()
        .clipped()

}

This is how you handle the image selection. To recap, we retrieve the image data when a user selects an image from the built-in Photos library. We save the image data to a state variable (i.e. selectedPhotoData). SwiftUI detects the value change and triggers a UI update to render the photo on screen.

swiftui-photos-picker-waterfall

Selecting Multiple Photos

The PhotosPicker view can also support multiple photo selection. Let’s build another quick demo to see how it works. Again, we have two state variables to hold the PhotosPickerItem objects and Data object. Since the user may select more than one photos, both variables become an array:

@State private var selectedItems: [PhotosPickerItem] = []
@State private var selectedPhotosData: [Data] = []

To support multiple photo selection, the trick is to use another initialization method of PhotosPicker:

PhotosPicker(selection: $selectedItems, maxSelectionCount: 5, matching: .images) {
    Image(systemName: "photo.on.rectangle.angled")
}
.onChange(of: selectedItems) { newItems in
    for newItem in newItems {

        Task {
            if let data = try? await newItem.loadTransferable(type: Data.self) {
                selectedPhotosData.append(data)
            }
        }

    }
}

This method has an additional parameter named maxSelection. We set the value to 5, which means the user is allowed to support up to 5 photos. In this case, we may capture more than one photos in the onChange closure. What we did is to load each of the photo items and add it to the data array (i.e. selectedPhotosData).

For this demo view, instead of creating a button at the centre of the screen, we put the button in the navigation bar. Here is the full code snippet:

NavigationStack {

    ScrollView {
        VStack {
            ForEach(selectedPhotosData, id: \.self) { photoData in
                if let image = UIImage(data: photoData) {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                        .cornerRadius(10.0)
                        .padding(.horizontal)
                }
            }
        }
    }


    .navigationTitle("Photos")
    .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
            PhotosPicker(selection: $selectedItems, maxSelectionCount: 5, matching: .images) {
                Image(systemName: "photo.on.rectangle.angled")
            }
            .onChange(of: selectedItems) { newItems in
                for newItem in newItems {

                    Task {
                        if let data = try? await newItem.loadTransferable(type: Data.self) {
                            selectedPhotosData.append(data)
                        }
                    }

                }
            }
        }
    }
}

When there is any changes of the selectedPhotosData variable, SwiftUI will refresh the UI and display the photos in the scroll view.

swiftui-photospicker-multiple-photos

If you enjoy this article and want to dive deeper into SwiftUI, you may check out our Mastering SwiftUI book.

Read next