In this post, we’ll implement a SwiftUI view that allows the users to pick a photo from their photo library, with the SwiftUI PhotosPicker
.
The source code of this post is available here.
Step 0 - Overview
Before getting started, let’s learn more about PhotosPicker
. It is a public structure of type SwiftUI view; users can select one or multiple photos from their library via this picker. Note that this is a struct in PhotosUI
and is available on devices with iOS 16, macOS 13, watchOS 9 or higher.
Step 1 - Import PhotosUI
As mentioned in the overview, the PhotosPicker
is part of the PhotosUI
so let’s create a SwiftUI project and add the import expression above the ContentView
structure.
import SwiftUI
import PhotosUI
struct ContentView: View {
var body: some View {
//...
}
}
Step 2 - Add a state property and the PhotosPicker
Add a @State
property called selected
of type [PhotosPickerItem]
.
Now in the VStack of the body
of the ContentView
, let’s declare a PhotoPicker
element. The parameters of the initializer are explained below:
selection: Binding<[PhotosPickerItem]>
Here we need to pass the state property selected declared as instructed above. Since this is a binding array of PhotosPickerItem, the item(s) selected in the picker will be updated to theselected
array in our view.maxSelectionCount: Int?
This is an optional of Integer, which sets the maximum number of items that the user can pick from the library.matching filter: PHPickerFilter?
This parameter is aPHPickerFilter
, which is a struct inPhotosUI
that represents the type of elements (images or videos that can be stored and picked from a photo library) and acceptable values included:.images
,.videos
,.screenshots
etc. In this demo, we’ll use.images
to get a photo.@ViewBuilder label: Label
In short this parameter is where we define the front end of the picker. For instance, in this demo, we passed in a VStack with a photo icon and a text message "Select a photo".
struct ContentView: View {
@State var selected: [PhotosPickerItem] = []
var body: some View {
VStack {
PhotosPicker(selection: $selected, maxSelectionCount: 1, matching: .images) {
VStack {
Image(systemName: "photo")
.resizable()
.frame(width: 60, height: 45, alignment: .center)
Text("Select a photo")
}
}
}
.padding()
}
}
Once this step is done, you should be able to see a picker and on tap gesture, a picker view will be presented, where you can select the default images in the simulator or your photos if you’re running this project on a physical device.
Step 3 - Handle changes in the selection
Now you might be wondering, we have a state property to hold the selected item(s) from the photo library, how can the users see the images after selecting them?
We’re now addressing that. The state property selected
is an array of PhotosPickerItem
and need to fetch the data from it first. When do we need to perform this fetch? Every time the user changes the selection from the picker, right? Hence, we need an onChange
modifier to observe the updates in selected
.
First, add another state property to hold the data fetched from the selected items. We can make it an optional so that failure to parse the image data can be handled.
@State var data: Data?
Then add the on change modifier as shown below:
Every time when selected
gets an update, the code will try to read the selected
array to check if there is at least one item. If no item is found, no further action will be performed. (Logic of the guard else
statement.)
If at least one item is found, it invokes the function .loadTransferable
to parse the selected photo item and transfer the item to data. In the completion handler, we use a switch to handle both successful and failing cases. If the transfer fails, here only an error message gets printed. If the transfer goes through, we use if let
to avoid nil value in data. If not nil, the data gets assigned to the state property data
in the view.
PhotosPicker(selection: $selected, maxSelectionCount: 1, matching: .images) {
VStack {
//...
}
}
//-------new lines below
.onChange(of: selected) { newValue in
guard let selectedItem = selected.first else {
return
}
selectedItem.loadTransferable(type: Data.self) { result in
switch result {
case .success(let data):
if let data = data {
self.data = data
} else {
print("Found nil in data")
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
Step 4 - Present the selected image
Since we got the data of the image in the view now, we’re ready to present the selected image in the view.
Add the following code in the ContentView
VStack
, above the PhotosPicker
.
The if let
statement double checks if the data is non-nil, if not, an UIKit UIImage element will be initiated with the data.
Inside the if
statement, we can add a text like "Selected photo:" as a hint of what this image is. The image below the text is declared with the UIImage element. You can use a fit
aspectRatio
to make the image size fit the view more.
if let data = data, let uiImage = UIImage(data: data) {
VStack {
HStack {
Text("Selected photo: ")
.bold()
Spacer()
}
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fit)
}
.padding()
}
Spacer()
.frame(height: 50)