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.

Lesezeit: 8 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 11 Beiträge
SwiftUI in der Praxis, Teil 2

(Bild: Paweł Kuźniar (unter der GFDL))

Von
  • Thomas Sillmann
Inhaltsverzeichnis

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.

Notizen lassen sich nun erstellen und werden umgehend innerhalb der Liste aufgeführt (Abb. 1)