SwiftUI in der Praxis, Teil 1

heise Developer stellt in einem Zweiteiler den Einsatz von SwiftUI anhand der Entwicklung einer kleinen Beispiel-App für iOS vor.

Lesezeit: 7 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 13 Beiträge

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

Von
Inhaltsverzeichnis

Mit SwiftUI verändern sich einige Konzepte, die typisch für die UI-Gestaltung unter AppKit, UIKit und WatchKit sind. Das hängt primär mit dem Umstand zusammen, dass SwiftUI auf eine deklarative statt eine imperative Syntax setzt. Statt also Views durch den Aufruf von Methoden zu erstellen und ihr Verhalten zu steuern, kommt für sie in SwiftUI ein Status zum Einsatz. Er ist maßgeblich für das Aussehen und die Funktionsweise einer View verantwortlich. Ändert er sich, sorgt das auch für eine automatische Erneuerung der View.

Um die Arbeit mit SwiftUI und das grundlegende Konzept detailliert vorzustellen und zu erläutern, zeigt dieser Artikel die vollständige Umsetzung einer kleinen Beispiel-App für iOS auf Basis von SwiftUI. Sie stellt eine simple Notizverwaltung dar, in der sich Notizen samt Titel und Inhalt erstellen und bearbeiten lassen. Darüber hinaus gibt es eine Option, Notizen nachträglich als Favoriten zu kennzeichnen.

Um die Umsetzung des im Folgenden beschriebenen Beispiels nachzuvollziehen, braucht es nichts mehr als die aktuelle Version von Xcode. Als Erstes erstellt man ein neues iOS-Projekt auf Basis der "Single View App"-Vorlage und gibt ihr einen beliebigen Namen. Wichtig: Neben der Programmiersprache Swift muss im Bereich "User Interface" der Punkt "SwiftUI" ausgewählt sein (s. Abb. 1).

In Xcode lässt sich direkt ein neues Projekt auf Basis von SwiftUI erstellen (Abb. 1).

Ehe es im Detail um die Arbeit mit SwiftUI geht, muss man die notwendige Basis zur Arbeit mit der Notizen-App erstellen. Sie setzt sich aus zwei Klassen zusammen: Note und NotesManager. Ersteres dient zur Abbildung einer einzelnen Notiz, wofür die Klasse insgesamt drei Eigenschaften für Titel, Inhalt und eine mögliche Markierung als Favorit mitbringt. Die Eigenschaften werden direkt über einen passenden Initializer gesetzt, der für alle drei Properties einen Standardwert vorsieht. NotesManager fungiert als App-übergreifendes Singleton zum zentralen Zugriff auf alle Notizen. Dazu bringt sie ein Array mit, in dem man alle Notizen speichert.

Diese erste Basisversionen der beiden Klassen findet sich in Listing 1. Zu einem späteren Zeitpunkt nimmt der Autor an ihnen noch ein paar notwendige Anpassungen vor.

// Listing 1: Deklaration der Klassen Note und NotesManager
class Note {
    var title: String
    var content: String
    var isFavorite: Bool
    
    init(title: String = "", content: String = "", isFavorite: Bool = false) {
        self.title = title
        self.content = content
        self.isFavorite = isFavorite
    }
}

class NotesManager {
    static let shared = NotesManager()
    var notes = [Note]()
}

Für den Moment liegen genügend Informationen vor, um mit der Umsetzung des User Interface für die Notizen-App zu beginnen. Als Basis hierfür nutzen Entwickler die ContentView.swift-Datei, die Xcode automatisch zusammen mit dem Projekt erzeugt hat. Sie enthält Dummy-Code für eine erste View und einen Preview-Provider, der jene View über die Preview in Xcode einblendet.

Die App stellt alle in ihr enthaltenen Notizen in einer Listenansicht dar. Im ersten Schritt ergänzt man darum die ContentView.swift-Datei um eine gänzlich neue View namens NoteCell, die direkt unterhalb von ContentView implementiert wird. Über diese View lässt sich das Aussehen der Zellen innerhalb der Liste festlegen. Für jede anzuzeigende Notiz erstellen Entwickler im nächsten Schritt eine passende Instanz der View.

Die View ist nicht sonderlich komplex. Sie soll den Titel der zugehörigen Notiz sowie eine Sterngrafik anzeigen, sofern die Notiz als Favorit gekennzeichnet ist. Entsprechend ist die Basis der NoteCell-View ein HStack, der ein Text-Element für den Titel und ein Image für die Sterngrafik enthält. Letzteres wird nur angezeigt, wenn es sich bei der Notiz um einen Favoriten handelt. Um die View korrekt konfigurieren zu können, benötigt sie außerdem eine note-Property, über die sich ihr die darzustellende Notiz mit den zugehörigen Informationen übergeben lässt. Listing 2 zeigt die vollständige Implementierung der NoteCell-Struktur:

// Listing 2: Erstellen der Zelle für die Notizenliste
struct NoteCell: View {
    var note: Note
    
    var body: some View {
        HStack {
            Text(note.title)
            if note.isFavorite {
                Spacer()
                Image(systemName: "star.fill")
            }
        }
    }
}

Mit der Zelle als Basis setzen Entwickler nun die eigentliche Listenansicht um. Dazu überarbeiten sie die ContentView[code]-Struktur innerhalb der ContentView.swift-Datei. Sie erhält zuerst eine Property namens [code]notesManager, die das Singleton der NotesManager-Klasse zugewiesen bekommt. Sie fungiert als Quelle für die View, aus der sie die benötigten Informationen (sprich die anzuzeigenden Notizen) auslesen kann.

Um die Listenansicht umzusetzen, kommt eine SwiftUI-View namens List zum Einsatz. Sie erwartet eine Range und ein Design für die in ihr enthaltenen Zellen. Als Range nutzt man das notes-Array der eben erstellten notesManager-Property. Das sorgt dafür, dass für jede Notiz innerhalb des Arrays eine passende Zelle innerhalb der Liste erzeugt wird.

An dieser Stelle ist aber zusätzlich eine erste Anpassung am Model, genauer gesagt der Note-Klasse, vorzunehmen. List kann nämlich nur Instanzen eines Typs in Form eines Arrays durchlaufen, wenn der Typ konform zum Identifiable-Protokoll ist. Es besitzt als einzige Anforderung die Implementierung einer id-Property, die hashable ist. Im Beispiel erzeugen Entwickler dazu für jede neue Notiz schlicht eine Instanz auf Basis von UUID und weisen sie der neuen id-Property zu. Die überarbeitete Note-Klasse sieht man in Listing 3:

// Listing 3: Ergänzung der Note-Klasse um das Identifiable-Protokoll
class Note: Identifiable {
    var id = UUID()
    var title: String
    var content: String
    var isFavorite: Bool
    
    init(title: String = "", content: String = "", isFavorite: Bool = false) {
        self.title = title
        self.content = content
        self.isFavorite = isFavorite
    }
}

Nun ist List in der Lage, ein Array von Note-Instanzen zu durchlaufen. Listing 4 zeigt die entsprechende Implementierung von List innerhalb der body-Property der ContentView-Structure (der hier standardmäßig enthaltene "Hello world"-Text wurde entfernt). Für jede gefundene Notiz wird eine passende Instanz von NoteCell erzeugt.

// Listing 4: Erstellen einer Listenansicht für Notizen
struct ContentView: View {
    var notesManager = NotesManager.shared
    
    var body: some View {
        List(notesManager.notes) { note in
            NoteCell(note: note)
        }
    }
}