SwiftUI in der Praxis, Teil 2
heise Developer stellt in einem Zweiteiler den Einsatz von SwiftUI anhand der Entwicklung einer kleinen Beispiel-App fĂĽr iOS vor.
- Thomas Sillmann
In einem frĂĽheren Artikel wurde das Fundament fĂĽr eine kleine Notizen-App gelegt. Die bewerkstelligte eine Ansicht zur Auflistung aller Notizen und integriert UITextView
in SwiftUI, um darĂĽber Notizen mit Inhalten zu fĂĽllen. All diese kleinen PuzzlestĂĽcke werden nun genutzt, um die Anwendung zu finalisieren.
Der erste Schritt in diese Richtung besteht darin, mit der neu erstellten NoteView
die Möglichkeit einzubauen, neue Notizen zu erstellen. Hierbei soll die Startansicht der App (ContentView
) als Navigation-View umgesetzt werden, in der sich ein passendes Bar-Button-Item zum HinzufĂĽgen neuer Notizen findet. Ein Tap auf diesen Button soll die NoteView
in Form eines modalen Sheets einblenden. Über eine Abbrechen- beziehungsweise Speichern-Schaltfläche lässt sich das Sheet wieder ausblenden (es verwirft oder speichert die neu zu erstellende Notiz).
Begonnen sei zunächst mit der Ansicht, die über das Sheet eingeblendet werden soll. Auch die soll als Navigation-View umgesetzt werden, um so die Abbrechen- und Speichern-Buttons innerhalb einer Navigation-Bar unterbringen zu können. Als Basis der Navigation-View binden Entwickler dann eine NoteView
-Instanz ein und ĂĽbergeben ihr eine neue Note
-Instanz als Parameter. Das Antippen der Speichernschaltfläche speichert jene neue Notiz im Notes-Manager, andernfalls wird sie verworfen.
Eine Besonderheit gibt es bei der Implementierung der neuen View zu beachten, die mit dem Verhalten von Sheets in SwiftUI zusammenhängt. Denn Sheets werden nicht wie unter UIKit über eine Methode erstellt und aufgerufen. Stattdessen sind sie ein Status der View, die für den Aufruf des Sheets verantwortlich ist. Die neue View zum Erstellen von Notizen benötigt entsprechend ein Binding zu diesem Status, um ihn bei Betätigen der Abbrechen- beziehungsweise Speichern-Schaltfläche ändern und sich selbst so wieder ausblenden zu können. Einen Dismiss-Aufruf, wie er View-Controllern unter UIKit zur Verfügung steht, gibt es in SwiftUI nämlich nicht.
Um den Code an der Stelle etwas ĂĽbersichtlicher zu gestalten, mĂĽssen Entwickler fĂĽr die Abbrechen- und Speichern-Buttons eine eigens kreierte View namens DismissButton
einsetzen. Sie erwartet – neben dem genannten Binding zur Präsentation eines Sheets – einen optionalen Titel sowie eine Action. Der Grund hierfür ist, dass die beiden benötigten Schaltflächen den Binding-Status ändern sollen. Nur der Speichern-Button soll zusätzlich noch die neue Notiz im Model ablegen. Mit einer eigenen Button-View, die sich immer um das Ändern des Binding-Status kümmert und optional zusätzliche Befehle ausführt, bringt man mehr Struktur in das Projekt.
Entsprechend erstellen Entwickler nun eine neue SwiftUI-View namens CreateNoteNavigationView
, deren vollständige Implementierung in Listing 1 aufgeführt ist (inklusive separatem DismissButton
und Anpassung des Preview-Providers zum Umgang mit dem Binding-Status). Sie basiert auf einer NavigationView
, die eine NoteView
-Instanz implementiert. Ăśber den Modifier navigationBarItems(leading:trailing:)
erstellt man die Abbrechen- und Speichern-Schaltflächen, die am oberen linken beziehungsweise rechten Rand der Navigation-Bar angezeigt werden. Die Schaltflächen nutzen den neuen DismissButton
, der immer den Status des Bindings ändert und optional einen alternativen Titel und zusätzliche Befehle erwartet. So landet die neue Notiz beim Speichern im Model.
// Listing 1: View zum Erstellen neuer Notizen aus einer Sheet-Ansicht heraus
struct CreateNoteNavigationView: View {
@Binding var isPresented: Bool
private let note = Note()
var body: some View {
NavigationView {
NoteView(note: note)
.navigationBarItems(
leading: DismissButton(presentsModalView: $isPresented),
trailing: DismissButton(presentsModalView: $isPresented, title: "Save") {
NotesManager.shared.notes.append(self.note)
}
)
.navigationBarTitle("Create new note")
}
}
}
struct DismissButton: View {
@Binding var presentsModalView: Bool
var title = "Cancel"
var action: (() -> Void)?
var body: some View {
Button(action: {
if self.action != nil {
self.action!()
}
self.presentsModalView = false
}) {
Text(title)
}
}
}
struct CreateNoteNavigationView_Previews: PreviewProvider {
static var previews: some View {
CreateNoteNavigationView(isPresented: .constant(true))
}
}
Nun binden Entwickler die neue CreateNoteNavigationView
noch als Sheet ein, das sich aus der Listenansicht der ContentView
heraus aufrufen lässt (s. Listing 2). Dazu packen sie jene Liste in eine Navigation-View und ergänzen eine Plus-Schaltfläche als Bar-Button-Item am oberen rechten Rand. Um das Sheet aufrufen zu können, müssen sie die neue Navigation-View mit dem sheet(isPresented:content:)
-Modifier verknĂĽpfen. Der erste Parameter erwartet ein Binding fĂĽr den Status, der die Sichtbarkeit des Sheets steuert; false
bedeutet, dass das Sheet nicht sichtbar ist, während es durch Wechsel des Status auf true
eingeblendet wird. Letzteres wird durch die Plus-Schaltfläche innerhalb der Navigation-Bar ausgelöst. Der zweite Parameter ist die View, die als Sheet angezeigt werden soll, hier also eine Instanz von CreateNoteNavigationView
. Sie erhält den showsCreateNoteSheet
-Status als Binding, was es der View ermöglicht, das Sheet wieder auszublenden.
In gleichen Zug packen Entwickler die NoteCell
-Instanzen, die eine passende Zelle fĂĽr jede Notiz in der Liste anzeigen, in einen NavigationLink
. Das ist eine besondere Art von Button, der statt Befehlen eine View erwartet. Sie wird bei Betätigen des Buttons (in dem Fall bei Auswahl einer Zelle) erstellt und per Push auf dem Navigation-Stack eingeblendet. Das nutzt man, um eine NoteView
-Instanz mit der passenden Notiz zu laden. So lassen sich erstellte Notizen erneut einblenden und bearbeiten:
// Listing 2: Einbinden des Sheets und Aktivierung der Zellenauswahl
struct ContentView: View {
var notesManager = NotesManager.shared
@State private var showsCreateNoteSheet = false
var body: some View {
NavigationView {
List(notesManager.notes) { note in
NavigationLink(destination: NoteView(note: note)) {
NoteCell(note: note)
}
}
.navigationBarItems(trailing: Button(action: {
self.showsCreateNoteSheet = true
}) {
Image(systemName: "plus.circle")
})
.navigationBarTitle("Notes")
}
.sheet(isPresented: $showsCreateNoteSheet) {
CreateNoteNavigationView(isPresented: self.$showsCreateNoteSheet)
}
}
}
An der Stelle lässt sich die App nun ausführen und testen, um den bisherigen Stand zu überprüfen. Notizen lassen sich aus der Startansicht heraus erstellen und werden im Anschluss direkt mit ihrem Titel innerhalb der Liste angezeigt. Sie lassen sich auswählen und bearbeiten, wobei sich Änderungen am Titel direkt auf den Titel der Navigation-Bar auswirken.