Accessing the user gallery using a PHPicker

Implementing an ImagePicker 🖼

When the user taps on the gallery ToolbarItem, we want to open the image gallery for him to select a photo from his gallery.

We want to display the ImagePicker within a modal view. Therefore, we create a State in our ContentView named “showImagePicker”. 

@State var showImagePicker = false

If this showImagePicker is true, we want to display the modal view. For this purpose, we use the .sheet modifier. We will add our ImagePicker to the modal view later.

NavigationView {
    GeometryReader { geometry in
        //...
    }
        //...
        .sheet(isPresented: $showImagePicker, content: {
            Text("ImagePicker")
        })
}

We want to open the ImagePicker when the user taps on the gallery ToolbarItem. Therefore, we create a corresponding Binding in our GalleryButton view, which we toggle through the Button’s action closure.

struct GalleryButton: View {
    
    @Binding var showImagePicker: Bool
    
    var body: some View {
        Button(action: {
            showImagePicker = true
        }) {
            Image(systemName: "photo")
                .imageScale(.large)
        }
    }
}

Now, we have to initialize the binding in our ContentView.

.toolbar(content: {
    ToolbarItem(placement: .navigationBarLeading) {
        GalleryButton(showImagePicker: $showImagePicker)
    }
    //...
})

If we run the app now, we can display the modal view by tapping on the gallery icon. However, we didn’t implement an image picker yet.

You can close the modal view by swiping downwards

Accessing the user’s photo gallery using a PHPicker

Let’s implement our image picker. Create a new Swift file called “ImagePicker.swift”. We will use a PHPicker for our ImagePicker.

PHPicker is used to load pictures and videos from the user’s gallery. A PHPicker has two significant advantages: On the one hand, we only get access to the media of the user gallery if the user has explicitly released it, which protects the privacy of the user. On the other hand, a PHPicker allows multi-selection, which we will not need for our app’s purpose.

To create a PHPicker, declare a new struct in your ImagePicker.swift file called “PhotoPicker”. Since the PHPicker is part of the PhotoUI framework, we have to import it beside the SwiftUI framework.

import SwiftUI
import PhotosUI

struct ImagePicker {
    
}

To configure our PHPicker we have to declare PHPickerConfiguration property. By using a custom init function, we can specify the settings of our PHPicker, for example, the media type and selection limit.

struct ImagePicker {
    
    var configuration: PHPickerConfiguration
    
    init() {
        configuration = PHPickerConfiguration()
        configuration.selectionLimit = 1
        configuration.filter = .images
    }
    
}

Excursus: Interfacing with UIKit in SwiftUI

It is important to know that a PHPicker is not based on the SwiftUI but on the UIKit framework. A PHPicker is initialized by a special UIViewController, namely a PHPickerViewController.

However, we can not integrate UIViewController directly into our SwiftUI app. To host a UIViewController in our ImagePicker, we have to conform to the so-called UIViewControllerRepresentable protocol.

struct ImagePicker: UIViewControllerRepresentable {
    
    //...
    
}

To comply with this protocol, we have to implement three methods in our ImagePicker:

  • makeUIViewController: This method is used for creating the UIViewController we want to present
  • updateUIViewController: This method updates the UIViewController to the latest configuration every time it gets called
  • makeCoordinator: This method initializes a Coordinator that serves as a kind of a servant for handling delegate and data source patterns and user inputs. We will talk about this in more detail later.

Let’s add these methods to our ImagePicker

func makeUIViewController(context: Context) -> PHPickerViewController {

}
    
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
        
}
    
func makeCoordinator() -> Coordinator {
        
}

In the makeUIViewController method, we initialize a PHPickerViewController using our configuration.

func makeUIViewController(context: Context) -> PHPickerViewController {
        let controller = PHPickerViewController(configuration: configuration)
        return controller
}

We can leave the updateUIViewController method empty.

Before we implement the makeCoordinator method, we have to implement a corresponding subclass. This subclass contains the necessary function our PHPicker needs to retrieve the selected photo. We will complete this function later on.

struct ImagePicker: UIViewControllerRepresentable {
    
    //...
    
    class Coordinator: PHPickerViewControllerDelegate {
        
        private let parent: ImagePicker

        init(_ parent: ImagePicker) {
            self.parent = parent
        }
        
        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            
        }
    }
}

Now we can use a Coordinator instance for the makeCoordinator method.

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

Since our Coordinator subclass conforms to the PHPickerViewControllerDelegate protocol, we can assign it as the delegate of our PHPickerViewController inside our makeViewController method.

func makeUIViewController(context: Context) -> PHPickerViewController {
    let controller = PHPickerViewController(configuration: configuration)
    controller.delegate = context.coordinator
    return controller
}

We’re ready to integrate our PHPicker into our SwiftUI app! Now, we can use the .sheet modifier of our ContentView to call our ImagePicker.

.sheet(isPresented: $showImagePicker, content: {
    ImagePicker()
})

If we run the app now and click on the gallery icon, the image gallery will open inside the modal view. However, if we select any photo, nothing happens yet.

To fix this, our ImagePicker needs access to the imageController to change its unprocessedImage. Therefore, we add an @ObservedObject to our ImagePicker

struct ImagePicker: UIViewControllerRepresentable {
    
    @ObservedObject var imageController: ImageController
    
    //...
    
    init(imageController: ImageController) {
        //...
        self.imageController = imageController
    }
    
    //...
}

… which we initialize while passing the imageController of our ContentView:

.sheet(isPresented: $showImagePicker, content: {
    ImagePicker(imageController: imageController)
})

In our Coordinator subclass, we can now utilize the didFinishPicking function to update the unprocessedImage of our imageController.

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            
    let itemProvider = results.first?.itemProvider
            
   if let itemProvider = itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) {
       itemProvider.loadObject(ofClass: UIImage.self) { image, error in
           if let image = image as? UIImage {
               DispatchQueue.main.async {
                                 self.parent.imageController.unprocessedImage = image
                    }
               }
          }
     }
}

After a photo was picked, we want to dismiss the modal view. Therefore, we add a Binding to the showImagePicker State of the ContentView.

struct ImagePicker: UIViewControllerRepresentable {
    
    //...
    
    @Binding var showImagePicker: Bool
    
    //...
    
    init(imageController: ImageController, showImagePicker: Binding<Bool>) {
        //...
        self._showImagePicker = showImagePicker
    }
    
    //...
}

In our ContentView

.sheet(isPresented: $showImagePicker, content: {
    ImagePicker(imageController: imageController, showImagePicker: $showImagePicker)
})

We can now dismiss the modal view by assigning false to the showImagePicker property in our didFinishPicking function:

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            
    let itemProvider = results.first?.itemProvider
            
    if let itemProvider = itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) {
        //...
    }
            
    parent.showImagePicker = false
}

In our ImageController, we now only have to specify that if the unprocessedImage was changed, the displayedImage should be updated accordingly. 

var unprocessedImage: UIImage? {
    didSet {
        displayedImage = unprocessedImage
    }
}

Finally, we use the @Published property wrapper to update our ContentView (and therefore also our ThumbnailView) when the unprocessedImage was updated.

@Published var unprocessedImage: UIImage? {
   didSet {
       displayedImage = unprocessedImage
   }
}

If we run the app now, we can select any photo from the gallery. If we do this, the unprocessedImage and thus, also displayedImage of our imageController gets updated. Since we notify the ContentView about this change, it updates its body and displays the picked photo.

Congratulations, we successfully implemented the ImagePicker for our SwiftUI app using a PHPicker!

Leave a Reply

Your email address will not be published. Required fields are marked *