I need to open an image picker in my app using SwiftUI, how can I do that?
I thought about using the UIImagePickerController, but I don't know how to do that in SwiftUI.
I need to open an image picker in my app using SwiftUI, how can I do that?
I thought about using the UIImagePickerController, but I don't know how to do that in SwiftUI.
 
    
     
    
    You need to wrap UIImagePickerController in a struct implementing UIViewControllerRepresentable.
For more about UIViewControllerRepresentable, please check this amazing WWDC 2019 talk:
struct ImagePicker: UIViewControllerRepresentable {
    @Environment(\.presentationMode)
    private var presentationMode
    let sourceType: UIImagePickerController.SourceType
    let onImagePicked: (UIImage) -> Void
    final class Coordinator: NSObject,
    UINavigationControllerDelegate,
    UIImagePickerControllerDelegate {
        @Binding
        private var presentationMode: PresentationMode
        private let sourceType: UIImagePickerController.SourceType
        private let onImagePicked: (UIImage) -> Void
        init(presentationMode: Binding<PresentationMode>,
             sourceType: UIImagePickerController.SourceType,
             onImagePicked: @escaping (UIImage) -> Void) {
            _presentationMode = presentationMode
            self.sourceType = sourceType
            self.onImagePicked = onImagePicked
        }
        func imagePickerController(_ picker: UIImagePickerController,
                                   didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
            onImagePicked(uiImage)
            presentationMode.dismiss()
        }
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            presentationMode.dismiss()
        }
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(presentationMode: presentationMode,
                           sourceType: sourceType,
                           onImagePicked: onImagePicked)
    }
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = sourceType
        picker.delegate = context.coordinator
        return picker
    }
    func updateUIViewController(_ uiViewController: UIImagePickerController,
                                context: UIViewControllerRepresentableContext<ImagePicker>) {
    }
}
Here's a simple view to test it:
Show image picker buttonstruct ContentView: View {
    @State var showImagePicker: Bool = false
    @State var image: Image? = nil
    var body: some View {
        ZStack {
            VStack {
                Button(action: {
                    self.showImagePicker.toggle()
                }) {
                    Text("Show image picker")
                }
                image?.resizable().frame(width: 100, height: 100)
            }
            .sheet(isPresented: $showImagePicker) {
                ImagePicker(sourceType: .photoLibrary) { image in
                    self.image = Image(uiImage: image)
                }
            }
        }
    }
}
I hope this helps as a starting point!
I'm sure Apple will make this easier to do once SwiftUI is out of beta.
Tested on Xcode 11.4
Bugs:
sourceType is not the camera.
You won't be able to drag down the sheet - I haven't been able to find a solution yet. 
    
    Cleaned up version for Xcode 12 available via SPM as Swift Package:
https://github.com/ralfebert/ImagePickerView
Source:
import SwiftUI
public struct ImagePickerView: UIViewControllerRepresentable {
    private let sourceType: UIImagePickerController.SourceType
    private let onImagePicked: (UIImage) -> Void
    @Environment(\.presentationMode) private var presentationMode
    public init(sourceType: UIImagePickerController.SourceType, onImagePicked: @escaping (UIImage) -> Void) {
        self.sourceType = sourceType
        self.onImagePicked = onImagePicked
    }
    public func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = self.sourceType
        picker.delegate = context.coordinator
        return picker
    }
    public func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
    public func makeCoordinator() -> Coordinator {
        Coordinator(
            onDismiss: { self.presentationMode.wrappedValue.dismiss() },
            onImagePicked: self.onImagePicked
        )
    }
    final public class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        private let onDismiss: () -> Void
        private let onImagePicked: (UIImage) -> Void
        init(onDismiss: @escaping () -> Void, onImagePicked: @escaping (UIImage) -> Void) {
            self.onDismiss = onDismiss
            self.onImagePicked = onImagePicked
        }
        public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
            if let image = info[.originalImage] as? UIImage {
                self.onImagePicked(image)
            }
            self.onDismiss()
        }
        public func imagePickerControllerDidCancel(_: UIImagePickerController) {
            self.onDismiss()
        }
    }
}
 
    
    Based on @user:2890168 I made a version that:
UIImage instead of Image.sheet to present the ImagePicker. ActionSheet to help users to remove or change the image.struct LibraryImage: View {
    @State var showAction: Bool = false
    @State var showImagePicker: Bool = false
    @State var uiImage: UIImage? = nil
    var sheet: ActionSheet {
        ActionSheet(
            title: Text("Action"),
            message: Text("Quotemark"),
            buttons: [
                .default(Text("Change"), action: {
                    self.showAction = false
                    self.showImagePicker = true
                }),
                .cancel(Text("Close"), action: {
                    self.showAction = false
                }),
                .destructive(Text("Remove"), action: {
                    self.showAction = false
                    self.uiImage = nil
                })
            ])
    }
    var body: some View {
        VStack {
            if (uiImage == nil) {
                Image(systemName: "camera.on.rectangle")
                    .accentColor(Color.App.purple)
                    .background(
                        Color.App.gray
                            .frame(width: 100, height: 100)
                            .cornerRadius(6))
                    .onTapGesture {
                        self.showImagePicker = true
                    }
            } else {
                Image(uiImage: uiImage!)
                    .resizable()
                    .frame(width: 100, height: 100)
                    .cornerRadius(6)
                    .onTapGesture {
                        self.showAction = true
                    }
            }
        }
        .sheet(isPresented: $showImagePicker, onDismiss: {
            self.showImagePicker = false
        }, content: {
            ImagePicker(isShown: self.$showImagePicker, uiImage: self.$uiImage)
        })
        .actionSheet(isPresented: $showAction) {
            sheet
        }
    }
}
The default body of LibraryImage is an Image that shows a camera icon that is tappable by the users. 
On tap event, the image picker is shown with a sheet modifier. After the image selection, the LibraryImage body is recomputed and now shows the Image defined in else statement (because uiImage property now contains the image picked by the user).
Now, on tap event the ActionSheet is shown.
The edited image picker:
struct ImagePicker: UIViewControllerRepresentable {
    @Binding var isShown: Bool
    @Binding var uiImage: UIImage?
    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        @Binding var isShown: Bool
        @Binding var uiImage: UIImage?
        init(isShown: Binding<Bool>, uiImage: Binding<UIImage?>) {
            _isShown = isShown
            _uiImage = uiImage
        }
        func imagePickerController(_ picker: UIImagePickerController,
                                   didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            let imagePicked = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
            uiImage = imagePicked
            isShown = false
        }
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            isShown = false
        }
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(isShown: $isShown, uiImage: $uiImage)
    }
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        return picker
    }
    func updateUIViewController(_ uiViewController: UIImagePickerController,
                                context: UIViewControllerRepresentableContext<ImagePicker>) {
    }
}
default behaviour:
 
    
    iOS 14 Xcode 12 - Photo Picker SwiftUI with Reusable View with limits allowed
struct ImagePickerView: UIViewControllerRepresentable {
    
    @Binding var images: [UIImage]
    @Binding var showPicker: Bool
    var selectionLimit: Int
    
    func makeUIViewController(context: Context) -> some UIViewController {
        var config = PHPickerConfiguration()
        config.filter = .images
        config.selectionLimit = selectionLimit
        let picker = PHPickerViewController(configuration: config)
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, PHPickerViewControllerDelegate {
        var parent: ImagePickerView
        
        init(parent: ImagePickerView) {
            self.parent = parent
        }
        
        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            
            parent.showPicker.toggle()
            
            for img in results {
                if img.itemProvider.canLoadObject(ofClass: UIImage.self) {
                    img.itemProvider.loadObject(ofClass: UIImage.self) { (image, err) in
                        guard let image1 = image else { return }
                        
                        DispatchQueue.main.async {
                            self.parent.images.append(image1 as! UIImage)
                        }
                    }
                } else {
                    // Handle Error
                    parent.showPicker.toggle()
                }
            }
        }
    }
}
then in View you can do
VStack {          
    Image(systemName: "camera.viewfinder")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .onTapGesture {
            self.viewModel.pickerBool.toggle()
        }
}
.sheet(isPresented: self.$viewModel.pickerBool) {
    ImagePickerView(images: self.$viewModel.images, showPicker: self.$viewModel.pickerBool, selectionLimit: 3)
}
 
    
     
    
    We can use PhotosPicker since iOS 16.0, iPadOS 16.0, macOS 13.0, watchOS 9.0 and above.
Simple example:
import PhotosUI
import SwiftUI
struct MediaView: View {
    @State private var photosPickerPresented = false
    @State private var photoPickerItems = [PhotosPickerItem]()
    var body: some View {
        Button {
            // Present photo Picker
            photosPickerPresented.toggle()
        } label: {
            Text("Show Photos Picker")
        }
        .photosPicker(isPresented: $photosPickerPresented, selection: $photoPickerItems)
    }
}
 
    
    Here's a version that works in Xcode 11 beta 4.
It uses a BindableObject singleton (ImagePicker.shared) with two properties: .view and .image.
See usage below (ImagePickerTestView)
import SwiftUI
import Combine
final class ImagePicker : BindableObject {
    static let shared : ImagePicker = ImagePicker()
    private init() {}  //force using the singleton: ImagePicker.shared
    let view = ImagePicker.View()
    let coordinator = ImagePicker.Coordinator()
    // Bindable Object part
    let willChange = PassthroughSubject<Image?, Never>()
    @Published var image: Image? = nil {
        didSet {
            if image != nil {
                willChange.send(image)
            }
        }
    }
}
extension ImagePicker {
    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        // UIImagePickerControllerDelegate
        func imagePickerController(_ picker: UIImagePickerController,
                                   didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
            ImagePicker.shared.image = Image(uiImage: uiImage)
            picker.dismiss(animated:true)
        }
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            picker.dismiss(animated:true)
        }
    }
    struct View: UIViewControllerRepresentable {
        func makeCoordinator() -> Coordinator {
            ImagePicker.shared.coordinator
        }
        func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker.View>) -> UIImagePickerController {
            let picker = UIImagePickerController()
            picker.delegate = context.coordinator
            return picker
        }
        func updateUIViewController(_ uiViewController: UIImagePickerController,
                                    context: UIViewControllerRepresentableContext<ImagePicker.View>) {
        }
    }
}
struct ImagePickerTestView: View {
    @State var showingPicker = false
    @State var image : Image? = nil
    // you could use ImagePicker.shared.image directly
    var body: some View {
        VStack {
            Button("Show image picker") {
                self.showingPicker = true
            }
            image?
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 300)
        }.sheet(isPresented: $showingPicker,
                onDismiss: {
                    // do whatever you need here
                }, content: {
                    ImagePicker.shared.view
                })
        .onReceive(ImagePicker.shared.$image) { image in
            // This gets called when the image is picked.
            // sheet/onDismiss gets called when the picker completely leaves the screen
            self.image = image
        }
    }
}
#if DEBUG
struct ImagePicker_Previews : PreviewProvider {
    static var previews: some View {
        ImagePickerTestView()
    }
}
#endif
 
    
    I'm very new at Swift, but I was able to get it with the following.
This will load up an image picker modal and let you select a photo, and it will then update an @State variable from a parent. 
If this works for you, you can replace the @State with something that can span across multiple components, such as @EnvironmentObject so other components can get updated as well.
Hope this helps!
// ImagePicker.swift
struct ImagePicker : View {   
    @State var image: UIImage? = nil
    var body: some View {
        ImagePickerViewController(image: $image)
    }
}
// ImagePickerViewController.swift
import UIKit
import AVFoundation
import SwiftUI
struct ImagePickerViewController: UIViewControllerRepresentable {
    @Binding var image: UIImage?
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePickerViewController>) {
    }
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePickerViewController>) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.sourceType = UIImagePickerController.SourceType.photoLibrary
        imagePicker.allowsEditing = false
        imagePicker.delegate = context.coordinator
        return imagePicker
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate {
        var parent: ImagePickerViewController
        init(_ parent: ImagePickerViewController) {
            self.parent = parent
        }
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            let imagePicked = info[.originalImage] as! UIImage            
            parent.image = imagePicked
            picker.dismiss(animated: true, completion: nil)
        }
    }
}
Usage:
// SampleView.swift
struct SampleView : View {
    var body: some View {
        PresentationLink(destination: ImagePicker().environmentObject(self.userData), label: {
           Text("Import Photo")
       })
    }
}
Once again, I am fresh into Swift so if anyone has some comments, please let me know! Happy to learn more.
 
    
    I implemented a version that I think is more general and extensible. I used a Subject instead of a Binding to solve the problem where it's undoable/inappropriate to add another Binding in your view.
For example, you created a List showing a set of images stored in the underlying storage and you wanted to add an image with the image picker. In this case, it's very hard/ugly to have that image added to your underlying storage.
So I used a subject to transfer the image and you can simply observe it and add the new images to some storage, or if you want it to behave just like a Binding, it's one line of code, too. (modifying your State in your observation)
Then I wrapped the preferences into a ViewModel so it won't get cluttered if you want to have more subjects or configurations.
import SwiftUI
import Combine
struct ImagePickerView : UIViewControllerRepresentable {
    @Binding var model: ImagePickerViewModel
    typealias UIViewControllerType = UIImagePickerController
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    func makeUIViewController(context: UIViewControllerRepresentableContext<Self>) -> UIImagePickerController {
        let controller = UIImagePickerController()
        controller.delegate = context.coordinator
        controller.allowsEditing = false
        controller.mediaTypes = ["public.image"]
        controller.sourceType = .photoLibrary
        return controller
    }
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePickerView>) {
        // run right after making
    }
    class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        var parentView: ImagePickerView
        init(_ parentView: ImagePickerView) {
            self.parentView = parentView
        }
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parentView.model.isPresented = false
        }
        func imagePickerController(_ picker: UIImagePickerController,
                                          didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
            guard let uiImage = info[.originalImage] as? UIImage else { return }
            let image = Image(uiImage: uiImage)
            parentView.model.pickedImagesSubject?.send([image])
            parentView.model.isPresented = false
        }
    }
}
struct ImagePickerViewModel {
    var isPresented: Bool = false
    let pickedImagesSubject: PassthroughSubject<[Image], Never>! = PassthroughSubject<[Image], Never>()
}
Usage:
struct SomeView : View {
    @EnvironmentObject var storage: Storage
    @State var imagePickerViewModel = ImagePickerViewModel()
    var body: some View {
        Button(action: { self.imagePickerViewModel.isPresented.toggle() }) { ... }
            .sheet(isPresented: $imagePickerViewModel.isPresented) {
                ImagePickerView(model: self.$imagePickerViewModel)
            }
            .onReceive(imagePickerViewModel.pickedImagesSubject) { (images: [Image]) -> Void in
                withAnimation {
                    // modify your storage here
                    self.storage.images += images
                }
            }
    }
}
 
    
    I implemented it like this:
    import SwiftUI
    final class ImagePickerCoordinator: NSObject {
        @Binding var image: UIImage?
        @Binding var takePhoto: Bool
        init(image: Binding<UIImage?>, takePhoto: Binding<Bool>) {
            _image = image
            _takePhoto = takePhoto
        }
    }
    struct ShowImagePicker: UIViewControllerRepresentable {
        @Binding var image: UIImage?
        @Binding var takePhoto: Bool
        func makeCoordinator() -> ImagePickerCoordinator {
            ImagePickerCoordinator(image: $image, takePhoto: $takePhoto)
        }
        func makeUIViewController(context: Context) -> UIImagePickerController {
            let pickerController = UIImagePickerController()
            pickerController.delegate = context.coordinator
            guard UIImagePickerController.isSourceTypeAvailable(.camera) else { return pickerController }
            switch self.takePhoto {
            case true:
                pickerController.sourceType = .camera
            case false:
                pickerController.sourceType = .photoLibrary
            }
            pickerController.allowsEditing = true
            return pickerController
        }
        func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
    }
    extension ImagePickerCoordinator: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            guard let uiImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
            self.image = uiImage
            picker.dismiss(animated: true, completion: nil)
        }
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            picker.dismiss(animated: true, completion: nil)
        }
}
Add the logic of just two buttons to your View that's enough...))
