LimitedRound 2 is open. Get your first month free, no extra charge.Join the waitlist ›
Wire in your backend

How to add a photo picker to a SwiftUI app

TL;DR. PhotosPicker is the built-in SwiftUI control for choosing photos and videos, available from iOS 16. You bind it to a PhotosPickerItem, then load the selection as Data or an Image. Because it runs out of process, it needs no photo library permission and no usage string. AppFlight generates the picker and the loading code.

What a photo picker does for an iOS app

A photo picker lets users choose images or video from their library for a profile photo, a post, or an AI image input. The modern PhotosPicker is privacy-friendly by design: it shows the system picker in a separate process and returns only the items the user selects, so your app sees nothing else in the library. That means you skip the permission prompt entirely, which is both less friction for the user and one fewer thing for App Review to question. The selection arrives as a token you load asynchronously, since the photo may live in iCloud.

Adding a photo picker with AppFlight

There is no SDK or key for PhotosPicker, since it ships with the OS. In AppFlight you describe the feature, such as letting a user pick a profile image, and the AI generates a PhotosPicker bound to state plus the async code that loads the selection into an Image. Nothing is classified as client or backend because there is no credential. If you later send the image somewhere, like an AI model, AppFlight keeps that call behind a backend when it involves a secret key.

A SwiftUI example

Present a picker, then load the selected item into an Image:

import SwiftUI
import PhotosUI

struct PhotoPickerView: View {
    @State private var selection: PhotosPickerItem?
    @State private var image: Image?

    var body: some View {
        VStack(spacing: 16) {
            image?
                .resizable()
                .scaledToFit()
                .frame(maxHeight: 300)

            PhotosPicker("Choose a photo", selection: $selection, matching: .images)
        }
        .onChange(of: selection) { _, newItem in
            Task {
                if let data = try? await newItem?.loadTransferable(type: Data.self),
                   let uiImage = UIImage(data: data) {
                    image = Image(uiImage: uiImage)
                }
            }
        }
        .padding()
    }
}

For multiple selection, bind to selections: [PhotosPickerItem] and set maxSelectionCount. For video, change matching to .videos.

Alternatives

For capturing a new photo rather than choosing an existing one, you wrap the camera with UIImagePickerController, which does require a camera usage string. The older PHPickerViewController is the UIKit equivalent of PhotosPicker if you are not in SwiftUI. Direct PHPhotoLibrary access is the only path that exposes the whole library, but it triggers a permission prompt and stricter review, so reach for it only when a feature genuinely needs full library access.

FAQ

Does PhotosPicker need a photo library permission prompt?

No. PhotosPicker runs in a separate process and only hands your app the items the user picks, so there is no NSPhotoLibraryUsageDescription and no permission prompt. That is a key reason to prefer it over older library APIs.

Can it pick videos and multiple items?

Yes. You set the matching filter to .images, .videos, or a combination, and you can allow multiple selections by binding to an array of PhotosPickerItem instead of a single optional.

How do I get the actual image from the selection?

You call loadTransferable on the PhotosPickerItem to get Data or an Image. Loading is async because the photo may need to be fetched from iCloud, so do it inside a task and handle the optional result.

Sources

Build this app without opening Xcode.

AppFlight turns a plain-English prompt into a real native iOS app and ships it to the App Store. Round 2 is open: free for your first month.

Join the waitlist