Einstieg in SwiftUI

Anpassung über Library und Preview

Eine View muss man jedoch nicht ausschließlich im Code erstellen. Mit der Library von Xcode haben Entwickler Zugriff auf die verfügbaren Views und Modifier und können sie direkt daraus einer SwiftUI-View hinzufügen. Dazu ziehen sie einfach wie gewohnt das gewünschte Element direkt an die passende Stelle im Code. Views und Modifier werden hierbei getrennt voneinander in der Library aufgeführt und besitzen jeweils einen eigenen Reiter (s. Abb. 3).

Über die Library haben Entwickler Zugriff auf verschiedene Views sowie Modifier, die sie direkt mittels Drag & Drop im Code oder über die Preview hinzufügen können (Abb. 3).

Die Library ist übrigens nicht nur praktisch, um Elemente aus ihr via Drag & Drop einer View hinzuzufügen. Sie bietet auch einen Überblick über die verfügbaren Elemente des SwiftUI-Frameworks. Insbesondere die Liste der Modifier ist für den Einstieg hilfreich, nicht zuletzt da die Library über eine Suchfunktion verfügt. Zum Beispiel lässt sich schnell ermitteln, welche Möglichkeiten zur Anpassung von Schriften in SwiftUI zur Verfügung stehen (s. Abb. 4).

Dank der praktischen Suchfunktion der Library kann man unter anderem in Erfahrung bringen, welche Modifier für bestimmte Aufgaben geeignet sind (Abb. 4).

Neben der Library haben Entwickler aber auch die Möglichkeit, diverse Anpassungen an Views über die in Xcode integrierte Preview vorzunehmen. Dazu wählen sie das gewünschte Element aus (z.B. einen Text oder ein Bild) und wechseln in den Attributes Inspector. Dort findet man – ähnlich wie man das aus der Arbeit mit Storyboards kennt – diverse Konfigurationsmöglichkeiten (welche genau das sind, hängt von der gewählten View ab). Bei Text-Instanzen beispielsweise können Entwickler unter anderem die Farbe und Formatierung festlegen (s. Abb. 5). Auch ist es möglich, Views und Modifier aus der Library statt im Code an der gewünschten Stelle in der Preview einzufügen.

Nach Auswahl eines Elements können Entwickler es auch über den Attributes Inspector konfigurieren (Abb. 5).

Wer mit der Preview gearbeitet und Anpassungen vorgenommen hat, dem fällt womöglich auf, dass sich jede Änderung in der Vorschau unmittelbar auf den Code auswirkt. Ändert man beispielsweise die Farbe eines Texts über den Attributes Inspector, wird im Code der View der passende Modifier automatisch ergänzt. Diese Systematik hängt damit zusammen, dass es in SwiftUI keine Trennung zwischen Code und Interface-Dateien gibt, so wie das bei der Arbeit mit Storyboards der Fall ist. Tatsächlich zählt bei einer SwiftUI-View nur der Code. Die von Xcode generierte Preview ist ein Spiegel des Codes, und deshalb führen Anpassungen an ihr auch immer zu einer zugehörigen Anpassung des Quelltexts.

Views und ihr Status

Unter SwiftUI fällt die Rolle des View-Controllers weg. Stattdessen werden Daten, die eine View darstellen oder verarbeiten soll, direkt an diese weitergegeben. Solche Daten, die für die Darstellung einer View und für ihre Funktionsweise verantwortlich sind, bezeichnet man als "source of truth". Ihre einfachste Form stellt eine Property dar. Sie spiegelt einen Status einer View wider, der bei Initialisierung der View festgelegt wird. In Properties gespeicherte Daten werden somit also von außen gesetzt und sind anschließend unveränderbar.

Ein einfaches Beispiel für den Einsatz von Daten auf Basis einer Property zeigt das folgende Codebeispiel. Die "source of truth" stellt hier die title-Property dar, für die ein passender Wert beim Initialisieren einer ContentView-Instanz zu übergeben ist. Die View zeigt ein simples Label an, dessen Inhalt sich auf die title-Property bezieht.

struct ContentView: View {

var title: String

var body: some View {
Text(title)
}

}

let myContentView = ContentView(title: "Hello world")

Wie aber geht man mit Daten um, die veränderbar sind? Als Beispiel soll eine View mit einem Text und einem Button umgesetzt werden. Der Text wird standardmäßig als Headline formatiert, lässt sich alternativ aber auch als Large Title darstellen. Diese Einstellung soll in einer Property namens useLargeTitle gespeichert und bei Betätigen des Buttons invertiert werden.

Um das zu ermöglichen, ist eine entsprechende Property (in diesem Fall useLargeTitle) mit dem Property Wrapper @State zu deklarieren. Er bringt zum Ausdruck, dass die Property eine veränderbare Statusinformation enthält, die wichtig für die Darstellung der View ist. Das hat zur Folge, dass bei einer Änderung einer solchen @State-Property die View neu gezeichnet wird. Bei Betätigung des Buttons brauchen Entwickler also nur die useLargeTitle-Property zu ändern, und die View wird im Anschluss passend aktualisiert; ein expliziter Reload ist von Entwicklerseite nicht nötig.

Zwei Dinge sind beim Einsatz von @State zu beachten: So deklarierte Properties müssen immer über einen Standardwert verfügen und werden typischerweise als private gekennzeichnet. Letzteres hängt damit zusammen, dass @State-Properties nur für Daten genutzt werden sollen, die explizit mit der View zusammenhängen. Sie werden also beispielsweise nicht dazu verwendet, um Informationen aus dem Model zu laden und zu speichern.

Wie der Einsatz von @State beispielhaft aussehen kann, zeigt das nächste Codebeispiel. Die useLargeTitle-Property ist passend deklariert und besitzt den Standardwert false. In der Implementierung der body-Property der View wird dann innerhalb eines V-Stacks zunächst der Wert der useLargeTitle-Property überprüft und abhängig vom Ergebnis eine von zwei Text-Varianten erstellt. Im Anschluss folgt noch die Erstellung der Schaltfläche mit dem SwiftUI-Typ Button. Der erwartet zwei Parameter, bei denen es sich um Closures handelt. Das erste definiert die Aktionen, die bei Betätigung des Buttons ausgeführt werden sollen (in dem Fall eine Invertierung der useLargeTitle-Property). Das zweite Closure definiert das Aussehen des Buttons. Im Beispiel wird dazu ein simpler Text mit dem Inhalt "Change presentation" genutzt:

struct ContentView: View {

@State private var useLargeTitle = false

var body: some View {
VStack {
if useLargeTitle {
Text("Hello world")
.font(.largeTitle)
} else {
Text("Hello world")
.font(.headline)
}
Button(action: {
self.useLargeTitle.toggle()
}) {
Text("Change presentation")
}
}
}

}

Neben diesen beiden gezeigten Varianten gibt es noch eine dritte Möglichkeit, einen veränderbaren Status einer View abzubilden. Sie funktioniert wie der Einsatz von @State, allerdings mit dem Unterschied, dass die View diese Statusinformation nicht selbst hält. Sie wird stattdessen von einer anderen View gespeichert.

Zum besseren Verständnis soll das Thema anhand des nächsten Beispiels erläutert werden. Basis ist die ContentView, die über eine @State-Property namens isActive verfügt. Anhand dieser Information wird entweder der Text "aktiv" oder "inaktiv" innerhalb der View ausgegeben.

struct ContentView: View {

@State var isActive = false

var body: some View {
HStack {
Text(isActive ? "Aktiv" : "Inaktiv")
StarButton(isActive: $isActive)
}
}

}

struct StarButton: View {

@Binding var isActive: Bool

var body: some View {
Button(action: {
self.isActive.toggle()
}) {
Image(systemName: isActive ? "star.fill" : "star")
}
}

}

Daneben besitzt ContentView ein weiteres Element, das neben dem Text angezeigt wird. Es handelt sich um eine zweite eigens kreierte View namens StarButton. Sie stellt eine simple Schaltfläche dar, die entweder einen gefüllten oder ungefüllten Stern anzeigt. Gespeichert ist diese Informationebenfalls in einer Property namens isActive. Bei Betätigung des Buttons wird der Wert jener Property invertiert.

Gegeben sind somit zwei Views, die beide als Status jeweils eine eigene isActive-Property besitzen. In diesem Szenario ist der Wert der isActive-Property des StarButton aber einzig und allein von der ContentView abhängig. Sie bestimmt über ihre eigene isActive-Property, ob die Schaltfläche aktiv ist oder nicht. StarButton muss also nicht selbst diese Information speichern, sondern stattdessen auf die aus der ContentView zurückgreifen, sie auslesen und bei Betätigung verändern.

Um das umzusetzen, nutzen Entwickler den Property-Wrapper @Binding. Damit deklarieren sie Properties, die einen Status einer View widerspiegeln, die entsprechende Information aber von einer anderen Stelle erhalten (in diesem Fall von der ContentView). Im Gegensatz zu @State-Properties besitzen solche auf Basis von @Binding keinen Standardwert und werden auch nicht als private gekennzeichnet (schließlich ist eine entsprechende Information für diese Properties zwingend von außen an die View übergeben).

@Binding-Properties lassen sich nur anderen Status-Properties zuweisen (also beispielsweise solche, die mit @State deklariert sind). Bei einer solchen Zuweisung ist zusätzlich der jeweiligen Status-Property ein $-Zeichen voranzustellen.

Zum besseren Verständnis sollen die drei genannten Vorgehensweisen zum Abbilden eines View-Status noch einmal zusammenfasst werden:

  • Property: nicht veränderbare Daten einer View. Sie werden einmalig bei der Initialisierung übergeben
  • @State-Property: veränderbare Daten einer View. Sie werden typischerweise als private deklariert und benötigen zwingend einen Standardwert.
  • @Binding-Property: veränderbare Daten einer View. Die entsprechende Information ist in einer anderen View gespeichert.

Gemein ist allen drei, dass sie sich auf Daten beziehen, die eng mit der jeweiligen View verzahnt und unabhängig vom Model einer App sind.