Je dois ouvrir ImagePicker dans mon application à l'aide de SwiftUI, comment faire?
J'ai pensé à utiliser UIImagePickerController, mais je ne sais pas comment faire cela dans SwiftUI.
J'ai implémenté une version qui me semble plus générale et extensible. J'ai utilisé un Subject
au lieu d'un Binding
pour résoudre le problème où il n'est pas possible/inapproprié d'ajouter une autre liaison dans votre vue.
Par exemple, vous avez créé un List
montrant un ensemble d'images stockées dans le stockage sous-jacent et vous vouliez ajouter une image avec le sélecteur d'images. Dans ce cas, il est très difficile/moche de l'avoir image ajoutée à votre stockage sous-jacent.
J'ai donc utilisé un sujet pour transférer l'image et vous pouvez simplement l'observer et ajouter les nouvelles images à un stockage, ou si vous voulez qu'il se comporte comme une liaison, c'est aussi une ligne de code. (modification de votre état dans votre observation)
Ensuite, j'ai enveloppé les préférences dans un ViewModel
afin qu'il ne soit pas encombré si vous voulez avoir plus de sujets ou de 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
}
}
}
}
Je l'ai implémenté comme ceci:
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)
}
}
Ajoutez la logique de seulement deux boutons à votre vue, cela suffit ...))
Je suis très nouveau chez Swift, mais j'ai pu l'obtenir avec les éléments suivants.
Cela chargera un sélecteur d'images modal et vous permettra de sélectionner une photo, puis il mettra à jour un @State
variable d'un parent.
Si cela fonctionne pour vous, vous pouvez remplacer le @State
avec quelque chose qui peut s'étendre sur plusieurs composants, comme @EnvironmentObject
pour que d'autres composants puissent également être mis à jour.
J'espère que cela t'aides!
// 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")
})
}
}
Encore une fois, je suis nouveau dans Swift donc si quelqu'un a des commentaires, faites-le moi savoir! Heureux d'en savoir plus.