SwiftUI: Displaying a Photo Picker

Chase
5 min readNov 6, 2023

Did you know that Apple created a way for SwiftUI to access the users photos without requesting permission, and it still ensures privacy for the user? We will go over how this works and how you can use it in your own projects.

A screenshot of the built in photos picker running on an iOS simulator waiting for us to choose the photos we want to select

Before we get lost in all the cool things we are about to learn, please take a couple seconds to follow me and clap for the article so that we can help more people learn about this useful content.

Apple Says

Apple encourages developers to use the built in picker if your app “only need read-only access to retrieve images to share on the internet or embed in a document or email” and that “Because the system manages its life cycle in a separate process, it’s private by default. The user doesn’t need to explicitly authorize your app to select photos, which results in a simpler and more streamlined user experience.” For more information and documentation from Apple, feel free to checkout the following links:

How is this more private

If we were to request access to the users photos library, that would be giving our app access to all the photos in the users library. Using the method below, our app will only have access to the specific photos that the user has explicitly chosen to put in our app. This is great for us as the developer because it gives our apps only what photos are needed, and it is great for the user because they can rest assured knowing that the app in their pocket isn’t creeping 🧟‍♂️ through all their photos.

Accessing the Users Photos in SwiftUI

In our example below, we have created a view model and view to work with the built in photo selector.

There are a few things to call out from the PhotosPicker API below, the first is that we are limiting what the user can select only to images for our example. We are also setting a maximum number of photos that the user can select. If for example we wanted the use this code to allow the user to choose a profile photo (or only a single image at a time) we would change the limit to 1. We have also asked the photos to be returned to use in the same order that they were selected, which is a nice UX feature of the API.

In our view model we have made a couple of arrays, one to hold the images that we want to display, and the other to hold the objects selected from the photos picker. Having these setup as two different arrays is important since the photo picker isn’t always limited to only photos. This brings us to the last piece of the view model, our function that takes the photo picker selection and tries to convert it into an image. In our use case, we want to reset the selected photos in the picker and the images previously chosen each time the user clicks the button. If you don’t want or need to reset these values in your code, you can remove the two calls to “*.removeAll()” from the function. We also check to make sure the selectedPhotos array is not empty so that we don’t waste time spinning up a Task if we don’t need it. If the array isn’t empty, we will try to convert the data from selectedPhotos into an array of images, and then we will add those images to the images array.

One thing to note, is that even though we are displaying the photos in a LazyHGrid, this does not appear to be a very performant way to display the images. With the code below, our memory usage grows quickly with each added photo. This is also not needed to make the photo picker work, but is included in this tutorial so that you can test your own code and ensure that it is working correctly in your project.

import SwiftUI
import PhotosUI

class PhotoSelectorViewModel: ObservableObject {
@Published var images = [UIImage]()
@Published var selectedPhotos = [PhotosPickerItem]()

@MainActor
func convertDataToImage() {
// reset the images array before adding more/new photos
images.removeAll()

if !selectedPhotos.isEmpty {
for eachItem in selectedPhotos {
Task {
if let imageData = try? await eachItem.loadTransferable(type: Data.self) {
if let image = UIImage(data: imageData) {
images.append(image)
}
}
}
}
}

// uncheck the images in the system photo picker
selectedPhotos.removeAll()
}
}

struct PhotoSelectorView: View {
@StateObject var vm = PhotoSelectorViewModel()
let maxPhotosToSelect = 10

var body: some View {
VStack {
ScrollView(.horizontal) {
LazyHGrid(rows: [GridItem(.fixed(300))]) {
ForEach(0..<vm.images.count, id: \.self) { index in
Image(uiImage: vm.images[index])
.resizable()
.scaledToFit()
}
}
}
PhotosPicker(
selection: $vm.selectedPhotos, // holds the selected photos from the picker
maxSelectionCount: maxPhotosToSelect, // sets the max number of photos the user can select
selectionBehavior: .ordered, // ensures we get the photos in the same order that the user selected them
matching: .images // filter the photos library to only show images
) {
// this label changes the text of photo to match either the plural or singular case based on the value in maxPhotosToSelect
Label("Select up to ^[\(maxPhotosToSelect) photo](inflect: true)", systemImage: "photo")
}
}
.padding()
.onChange(of: vm.selectedPhotos) { _, _ in
vm.convertDataToImage()
}
}
}

#Preview {
PhotoSelectorView()
}

If you want to see how to do the same thing in Android using Jetpack Compose, check out my other article here: https://medium.com/@jpmtech/jetpack-compose-display-a-photo-picker-6bcb9b357a3a

If you got value from this article, please consider following me, clapping for this article, or sharing it to help others more easily find it.

If you have any questions on the topic, or know of another way to accomplish the same task, feel free to respond to the post or share it with a friend and get their opinion on it.

If you want to learn more about native mobile development, you can check out the other articles I have written here: https://medium.com/@jpmtech

If you want to see apps that have been built with native mobile development, you can check out my apps here: https://jpmtech.io/apps

Thank you for taking the time to check out my work!

--

--