Auf Fotoauswahl reagieren
Im ersten Teil dieser Artikelreihe befassten wir uns mit der grundlegenden Implementierung eines PHPickerViewController
in SwiftUI. Diese bisherige Umsetzung erweitern wir nun um eine funktionierende Auswahlmöglichkeit eines Fotos, welches wir im Anschluss in unserer Beispiel-App präsentieren.
Analog zum Einsatz des UIImagePickerController
zum Zugriff auf die iPhone-Kamera nutzt auch PHPickerViewController
für die Steuerung der Fotoauswahl einen Delegate. Grundlage dieses Delegates ist das PHPickerViewControllerDelegate
-Protokoll. Um die Konfiguration unserer SwiftUI-View also fortsetzen zu können, benötigen wir einen Coordinator, der konform zu jenem Protokoll ist.
PHPickerViewControllerDelegate
besitzt lediglich eine einzige Anforderung in Form der Methode picker(_:didFinishPicking:)
. Das nachfolgende Listing zeigt, wie wir die bestehende ImagePickerView
um einen PHPickerViewControllerDelegate
-konformen Coordinator erweitern. Die dafür notwendigen Schritte sind:
- 01: Die Umsetzung des Coordinators erfolgt als Nested Class innerhalb von
ImagePickerView
. Die Klasse adaptiert dasPHPickerViewControllerDelegate
-Protokoll und implementiert die notwendige Methodepicker(_:didFinishPicking:)
(noch ohne Logik). Zusätzlich erhält der Coordinator bei der Initialisierung einen Verweis auf die zugehörige SwiftUI-View-Instanz. - 02: Über die
makeCoordinator()
-Methode bindet man den eben definierten Coordinator in der SwiftUI-View ein. - 03: In der
makeUIViewController(context:)
-Methode weisen wir der von uns erzeugtenPHPickerViewController
-Instanz unseren Coordinator als Delegate zu. Die Zuweisung erfolgt über diedelegate
-Property desPHPickerViewController
, die Coordinator-Instanz lesen wir über dencontext
-Parameter aus.
import PhotosUI
import SwiftUI
struct ImagePickerView: UIViewControllerRepresentable {
// #02: Coordinator in SwiftUI-View erstellen.
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> PHPickerViewController {
var imagePickerConfiguration = PHPickerConfiguration()
imagePickerConfiguration.filter = .images
imagePickerConfiguration.preferredAssetRepresentationMode = .current
imagePickerConfiguration.selectionLimit = 1
let imagePickerViewController = PHPickerViewController(configuration: imagePickerConfiguration)
// #03: Coordinator als Delegate zuweisen.
imagePickerViewController.delegate = context.coordinator
return imagePickerViewController
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
// #01: Coordinator implementieren.
class Coordinator: NSObject, PHPickerViewControllerDelegate {
var parent: ImagePickerView
init(_ imagePickerView: ImagePickerView) {
parent = imagePickerView
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
// TODO: Implement.
}
}
}
Im nächsten Schritt folgt nun die Implementierung der Delegate-Methode picker(_:didFinishPicking:)
. Sie wird von PHPickerViewController
aufgerufen, sobald der Nutzer ein Foto aus der Library auswählt oder den Abbrechen-Button betätigt. Auf die gewählten Fotos kann man über den results
-Parameter zugreifen, bei dem es sich um ein Array von PHPickerResult
-Instanzen handelt.
PHPickerResult
ermöglicht über die itemProvider
-Property Zugriff auf das enthaltene Foto. Da diese Property einen generischen NSItemProvider
zurück liefert, ist es notwendig, das enthaltene Bild durch Aufruf der loadDataRepresentation(forTypeIdentifier:)
-Methode als Daten-Objekt auszulesen (siehe Punkt 03 im nachfolgenden Listing). Für Bilder kommt der Type Identifier public.image zum Einsatz.
Um das Sheet nach erfolgreicher Bildauswahl zu schließen und das gewählte Bild weiter zu verarbeiten, ergänzen wir ImagePickerView
um zwei neue Eigenschaften:
- 01: Das Binding
showsImagePickerView
dient dazu, die Sichtbarkeit des Foto-Library-Sheets zu steuern. Durch Setzen auffalse
können wir so das Sheet programmatisch wieder ausblenden (beispielsweise nach der Auswahl eines Fotos). - 02: Das neue
action
-Closure erhält die Daten des vom Nutzer gewählten Bildes alsData?
-Instanz. So kann an der Stelle, an derImagePickerView
aufgerufen wird, das gewählte Bild weiter verarbeitet werden. Das Closure wird innerhalb vonpicker(_:didFinishPicking:)
aufgerufen, sofern erfolgreich Bilddaten aus der Nutzerauswahl bezogen werden konnten.
Um das Sheet sauber ausblenden zu können, habe ich mich zusätzlich für die Implementierung einer Hilfsmethode namens dismissImagePickerViewController(_:)
entschieden. Sie setzt showsImagePickerView
auf false
und stellt hierbei sicher, dass der Aufruf immer auf dem Main-Thread erfolgt. Der Aufruf dieser Hilfsmethode erfolgt immer am Ende von picker(_:didFinishPicking)
und greift damit sowohl bei der Fotoauswahl als auch beim Betätigen des Abbrechen-Buttons.
Apropos Fotoauswahl: In der Konfiguration von PHPickerViewController
haben wir festgelegt, dass maximal ein Bild ausgewählt werden kann. Entsprechend verarbeite ich innerhalb von picker(_:didFinishPicking:)
umgehend das erste zur Verfügung stehende Ergebnis.
import PhotosUI
import SwiftUI
struct ImagePickerView: UIViewControllerRepresentable {
// #01: Deklaration eines Binding-Parameters zum Ausblenden des Sheets
@Binding var showsImagePickerView: Bool
// #02: Deklaration eines Closure-Parameters zur Übergabe der Daten des gewählten Bildes.
var action: (Data?) -> Void
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> PHPickerViewController {
var imagePickerConfiguration = PHPickerConfiguration()
imagePickerConfiguration.filter = .images
imagePickerConfiguration.preferredAssetRepresentationMode = .current
imagePickerConfiguration.selectionLimit = 1
let imagePickerViewController = PHPickerViewController(configuration: imagePickerConfiguration)
imagePickerViewController.delegate = context.coordinator
return imagePickerViewController
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
var parent: ImagePickerView
init(_ imagePickerView: ImagePickerView) {
parent = imagePickerView
}
// #03: Implementierung der Delegate-Methode
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
if let result = results.first {
result.itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image") { object, error in
self.parent.action(object)
}
}
self.dismissImagePickerViewController()
}
// #04: Umsetzung einer Hilfsmethode zum Ausblenden des Sheets.
private func dismissImagePickerViewController() {
Task {
await MainActor.run {
parent.showsImagePickerView = false
}
}
}
}
}
Damit ist es geschafft: Bilder lassen sich nun aus unserer ImagePickerView
auswählen und mithilfe des action
-Closures weiter verarbeiten.
Zum Abschluss folgt noch die Aktualisierung der ContentView
. Diese speichert die über das action
-Closure erhaltenen Bilddaten in einer neuen State
-Property namens photoData
. Liegen entsprechende Bilddaten vor und lassen diese sich erfolgreich in eine UIImage
-Instanz umwandeln, nutzen wir diese, um das gewählte Bild als SwiftUI-Image
darzustellen.
struct ContentView: View {
@State private var photoData: Data?
@State private var showImagePickerView = false
var body: some View {
VStack {
if let photoData = self.photoData, let uiImage = UIImage(data: photoData) {
Image(uiImage: uiImage)
.resizable()
.scaledToFit()
}
Spacer()
Button("Select photo") {
showImagePickerView = true
}
}
.sheet(isPresented: $showImagePickerView) {
ImagePickerView(showsImagePickerView: $showImagePickerView) { imageData in
photoData = imageData
}
}
}
}
Den aktualisierten Code für dieses Projekt findet ihr auf GitHub unter https://github.com/Sillivan88/TS-ImagePickerView-Example.
Euer Thomas
Weiterführende Links zum Artikel
- Teil 1: Grundlegende Konfiguration: https://letscode.thomassillmann.de/zugriff-auf-die-foto-library-in-swiftui-via-phpickerviewcontroller-teil-1/
- Ergänzendes Video auf YouTube: https://www.youtube.com/watch?v=kDn8cljxsLU
- Beispielprojekt auf GitHub: https://github.com/Sillivan88/TS-ImagePickerView-Example
Schreibe einen Kommentar