What’s New With PhotosPicker in iOS 16

PhotosPicker in iOS 16 has a robust set of features for users to view and select assets, such as images and videos. By Felipe Laso-Marsetti.

Login to leave a rating/review
Download materials
Save for later
Share

Working with photos is commonplace in today’s era of mobile apps, and PhotosPicker is usually the go-to choice, especially when working with SwiftUI.

Happy Swift Bird

iOS 16 brings some important and useful enhancements to improve your development workflow with PhotosPicker:

  • Improved SwiftUI implementation.
  • New filter options that can be back-ported to iOS 15.
  • Compound filters.
  • Availability for macOS and watchOS.
  • Updated design for iPadOS.

In this tutorial, you will learn how to work with the PhotosPicker API and also see what is new as of iOS 16.

For this tutorial you should have at least some beginner knowledge of Swift and SwiftUI.

Without further ado, you’ll jump right into an awesome tutorial that leverages PhotosPicker and iOS 16.

Understanding PhotosPicker

PhotosPicker is a SwiftUI view that allows you to select one or more assets from your photo library. It’s part of the PhotoKit framework for working with videos and images from your device’s photos app.

PhotosPicker

With as little code as the following:

@State private var photosPickerItem: PhotosPickerItem?
PhotosPicker(selection: $photosPickerItem, matching: .images) {
  Label("Select Photo", systemImage: "photo")
}

You can have a button that, when tapped, shows a robust, native view for your users to pick photo or video assets from their photo library.

PhotosPicker Button

Some of the customization options you have are changing the button’s image, label or tint color.

You can also add filters to control what types of assets can be selected. If you’re working on an app that relies exclusively on Live Photos, hiding all other asset types will save you and your users time.

Once your users have made their asset selection, you’ll work with PhotosPickerItem, a representation of your selections in PhotosPicker, to get information about the selection or the actual assets themselves.

How about seeing all of this in action?

Getting Started

Download the starter project by clicking Download Materials at the top or bottom of the tutorial.

You’ll see a project called Foodvorite, a journal app that keeps track of your favorite foods with images. The project uses sample, hard-coded data for meals, since this tutorial’s focus isn’t on adding or editing meals or on persisting them with your photo selections.

Open the project to see how it’s set up. Then, open Meal.swift to look at the model that represents a meal entry in the app. A meal consists of an ID, title, notes and data for the associated photo.

You’ll use four views in the project:

  • MealsView: Shows a list of all your meals.
  • MealRow: View that makes up a row for a meal entry. The row shows the meal’s title, notes and a thumbnail of the photo (if a selection has been made).
  • MealDetail: View to show all the details of a meal and a large image of the selected photo (if available).
  • PhotoView: Helper view to show either a placeholder image or a photo in two different sizes — one for the thumbnail and a larger one for the meal details.

Build and run the project to see the app in action and familiarize yourself with it.

Foodvorite App showing three entries

Implementing a Photo Picker

Time to implement a PhotosPicker view, so meals no longer show the placeholder image.

Open MealDetail.swift, and add the following property as part of the view:

@State private var photosPickerItem: PhotosPickerItem?

This property is used with PhotosPicker to handle any individual asset selections the user makes.

Next, at the bottom of the VStack but still inside it, add the following code:

PhotosPicker(selection: $photosPickerItem) {
  Label("Select Photo", systemImage: "photo")
}

Here, you add the PhotosPicker view with the photo SF Symbol as the button icon and Select Photo as the button text.

The initializer for the PhotosPicker view takes one parameter, a binding to an optional PhotosPickerItem. To use PhotosPicker, you need to add an import for PhotosUI, which was already done for you as part of the starter project.

Build and run. Select any meal from the list to go to the details. Notice the button that’s now showing.

PhotosPicker Basic Button with Pumpkin Pie selected

Tap the photos picker. The photos picker will show all the available photos and videos on your device.

PhotosPicker Asset Selection

Excellent!

You’ll handle selections in a little bit, but for now, how about styling the button so it looks better?

Still inside VStack in MealDetail.swift, add the button inside the horizontal stack with some spacing and padding:

HStack(spacing: 10) {
  PhotosPicker(selection: $photosPickerItem) {
    Label("Select Photo", systemImage: "photo")
  }
}
.padding()

This helps add some padding so the button isn’t squished with the rest of the view’s content.

Next, add these modifiers to PhotosPicker:

.tint(.accentColor)
.buttonStyle(.borderedProminent)

The two modifiers above help give the button the project’s accent color and a large, prominent visual style.

Build and run. Navigate to a meal’s details. Look at the select photo button.

PhotosPicker Improved Button

It definitely looks better than before!

Handling Selections

So, you’ve added the Select Photo and the button it shows. You can even select a photo from the picker. This action dismisses the picker even though nothing happens at the moment. Time to take care of that!

Also in MealDetail.swift, add the following function inside struct and below body to load the photo’s data that your user selects in the picker:

private func updatePhotosPickerItem(with item: PhotosPickerItem) async {
  photosPickerItem = item
  if let photoData = try? await item.loadTransferable(type: Data.self) {
    meal.photoData = photoData
  }
}

This is an async method that takes PhotosPickerItem as a parameter. Within it, you assign the item parameter to the view’s photosPickerItem property.

You then call loadTransferable on PhotosPickerItem to acquire the asset’s data. This method is asynchronous and can throw an error, so you mark it with try? await accordingly.

If the method returns valid data, then you store it in the meal’s photoData property.

So, where can you call this method from? Still within MealDetail.swift, add the following modifier to ScrollView, after the navigation modifiers:

.onChange(of: photosPickerItem) { selectedPhotosPickerItem in
  guard let selectedPhotosPickerItem else {
    return
  }
  Task {
    await updatePhotosPickerItem(with: selectedPhotosPickerItem)
  }
}

This code block will get called whenever the photosPickerItem changes. Inside, you have a guard statement to ensure you have a valid PhotosPickerItem; otherwise, you return.

If there’s a non-nil item, you call the method you just wrote within a Task — since it’s an async method — and mark it with await.

Build and run. Navigate to a meal’s details. Tap Select Photo. Select a photo for your meal.

Meal Details With Photo

Yay! How cool is PhotosPicker?

Next, navigate back to the list of meals. Notice the row corresponding to the meal you just selected a photo for.

Meal List With Photo

Filter Options

Depending on the device you’re testing with, you may have noticed that the picker isn’t limiting selections to only photos — it includes everything from videos to screen recordings. You’ll need to use some of the available filter options to specify what types of selections the user can make.

Now, look at PHPickerFilter. It lets you choose from several filters:

  • bursts: Burst-style photos (ones with several photos taken at a high speed).
  • cinematicVideos: More modern, cinematic-style videos with focus transitions and shallow depth of field.
  • depthEffectPhotos: Photos taken with the depth effect.
  • images: Images, including Live Photos.
  • livePhotos: Live Photos exclusively.
  • panoramas: Panoramic photos.
  • screenRecordings: Videos of screen recordings.
  • screenshots: Screenshots (usually taken by hitting the power and volume up buttons on devices with Face ID).
  • slomoVideos: Slow-motion videos.
  • timelapseVideos: Time-lapse videos.
  • videos: Videos.

A couple helpful static functions also create a PHPickerFilter for you:

  • all(of: [PHPickerFilter]): Includes all of the filter options specified in the parameter array.
  • not(PHPickerFilter): Includes all options except for the specific filter in the parameter.

Here is an example of a picker filter that will show only images.

let filter: PHPickerFilter = .images

Here is an example of a picker filter that will show images, panoramic photos and screenshots in the picker.

Only images in the PhotosPicker UI.

let filter: PHPickerFilter = .all(of: [.images, .screenshots, .panoramas])

Here is an example of a picker filter that will show all asset types except videos.

let filter: PHPickerFilter = .not(.videos)

Here is an example of a picker filter that will show all asset except videos, slow-motion videos, bursts, Live Photos, screen recordings, cinematic videos and time-lapse videos.

let filter: PHPickerFilter = .not(.all(of: [
  .videos, 
  .slomoVideos, 
  .bursts, 
  .livePhotos, 
  .screenRecordings, 
  .cinematicVideos,
  .timelapseVideos
]))

As you can see, you can nest, mix and match filter options to suit your every need!

Adding Filtering to Photos Picker

How about adding some filters to only show images in the picker?

In MealDetail.swift, update the code that creates the PhotosPicker as follows:

PhotosPicker(selection: $photosPickerItem, matching: .images) {
  Label("Select Photo", systemImage: "photo")
}

For the matching parameter, you specify images as the only asset type that should show in the picker.

Build and run. Navigate to a meal’s details page. Open the photo picker.

You should only see images now. Whereas before, you would’ve seen all available asset types.

Polish & Wrap-Up

Depending on the device you’re using, loading and selecting an asset may or may not be immediate.

In this scenario, Apple recommends showing a non-blocking progress indicator, so your users know that you’re loading the asset after selection.

You may have noticed that there’s an isLoading property already available inside of MealDetail. Use it to show a ProgressView inside your body’s HStack, right after adding the PhotosPicker:

if isLoading {
  ProgressView()
    .tint(.accentColor)
}

When isLoading is true, your users will see a ProgressView next to the button.

Inside the onChange modifier, update the Task code block as follows:

Task {
  isLoading = true
  await updatePhotosPickerItem(with: selectedPhotosPickerItem)
  isLoading = false
}

The code block helps determine whether the loading action is happening.

Build and run. Make a photo selection. You see something like this:

Meal Details Progress View

Things might go so fast that you don’t even get a glimpse of ProgressView. It’s a good practice to add one anyway, as you shouldn’t assume users will use your app on the same device as you or under the same conditions.

If their experience is slower than the iOS simulator or a modern iOS device, then some indication of progress will go a long way toward avoiding frustration.

Fantastic work!

Where to Go From Here?

You can download the completed project files by clicking Download Materials at the top or bottom of the tutorial. Feel free to check out the final project to compare your results.

As you’ve just seen, using PhotosPicker is easy and intuitive, and with very little code, you have a robust set of features available.

If you want to take the project further, how about adding the ability to add and edit meals as well as persisting them along with the selected photos? To learn how, check out the Saving Data in iOS course.

And check out the documentation for more on PhotosPicker.

Hopefully, you found this tutorial as fun to read and follow as it was for me to write.

Thanks for your time, and feel free to leave any questions or comments in our forums.

Happy coding, and see you next time! :]