Projektaufbau mit SwiftUI
Beim Anlegen eines neuen Projekts in Xcode muss von vornherein die Swift-UI-Funktionalität
aktiviert werden.
Xcode generiert dann automatisch nicht nur die AppDelegate, sondern auch eine SceneDelegate Klasse.
Außerdem werden notwendige Properties direkt in der Info.plist angelegt.
Das SceneDelegate stellt Funktionen bereit, um die Ansicht bei App-Start und auch anderen Ereignissen
festzulegen. Dabei wird ein UIScene Objekt übergeben, für das eine Ansicht definiert und gesetzt wird. Im
Gegensatz zur Verwendung von xib-Files oder Storyboards muss das Window mit Hilfe der übergebenen Scene
selbst erzeugt werden. Wie gewohnt erhält das Window einen rootViewController. Bei der Verwendung von Swift
UI muss ein UIHostingController anstatt eines normalen ViewControllers verwendet werden. Der Inhalt dieses
Controllers entspricht einer View, deren Inhalt angezeigt werden soll. Zum Schluss wird das neu erstellte
Window auf dem Screen angezeigt.
SwiftUI stellt eine Interface View bereit, von der alle Klassen, die Inhalt auf dem Screen anzeigen sollen,
abgeleitet werden müssen. Jede dieser View-Klassen enthält eine Instanzvariable body. Diese wird vom
Entwickler definiert, indem eine neue View mit beliebigem Inhalt erstellt wird. Alle Elemente in dieser View
werden dann später angezeigt. Jede dieser Views kann darüber hinaus weitere von View abgeleitete Elemente
enthalten. Dadurch lassen sich komplexe Oberflächen einfach strukturieren, während die Komponenten separat
aufgebaut sind und der Code um einiges übersichtlicher wird.
Erste Schritte
Im SceneDelegate wird festgelegt, welcher Inhalt angezeigt wird. Xcode generiert zu Beginn eine Klasse
ContentView, die einen Beispieltext enthält. Eine Instanz dieser Klasse wird zur Laufzeit erzeugt und
angezeigt. Wird der Code der ContentView verändert, erscheint somit der angepasste Inhalt nach einem Start
der App. Außerdem zeigt Xcode auf der rechten Seite neben dem Quellcode eine Vorschau, wodurch der
Entwickler in Echtzeit sehen kann, wie das User Interface später in der App aussehen wird.
In einem ersten Versuch fügen wir einige Zeilen zur Definition der body Instanzvariable hinzu.
Wir beginnen mit einem VStack, einer Vertical StackView in SwiftUI. Um dem body mehrere Elemente hinzufügen
zu können, ist das notwendig, da dieser aus nur einem View Element bestehen darf. Außerdem fügen wir in
diesem VStack einen Text und ein Image hinzu. In Echtzeit können wir sehen, wie die Ansicht aussehen wird.
Da der Text noch zu klein ist, wollen wir die Font anpassen. Attribute jedes UI-Elements lassen sich einfach
an die jeweilige Initialisierung anhängen und werden Modifier genannt. Außerdem ändern wir in diesem Zuge
auch gleich die Farbe des Texts. Das sieht dann folgendermaßen aus:
Nach der Anpassung des Textes wirkt das Image noch zu klein. Wir könnten die gleichen Modifier auch
diesem Element hinzufügen. SwiftUI ermöglicht allerdings auch, Modifier zu allen Elementen innerhalb einer
Struktur hinzuzufügen. Somit können wir die Modifier verschieben und stattdessen auf den VStack setzen.
Nach der letzten Änderung erhalten alle Elemente innerhalb des Stacks die definierten Modifier.
Jetzt wollen wir den Text mit einem Background hinterlegen. Dafür definieren wir einen background, passen
den cornerRadius an und fügen padding hinzu. In der Vorschau kann man sofort erkennen, dass das angezeigte
Ergebnis nicht dem entspricht, was man erwarten würde.
Der Grund dafür ist die Reihenfolge der Modifier. Wir passen zuerst den cornerRadius an, der ohne einen
speziellen Hintergrund aber nicht sichtbar ist. Den background kann man sehen, allerdings wird dieser erst
nach dem cornerRadius definiert und erscheint deshalb ohne abgerundete Ecken. Der Padding wird als letztes
hinzugefügt, wodurch lediglich ein Abstand zwischen dem Text und dem Bild entsteht und nicht der eigentliche
Hintergrund größer wird. Nach einer Korrektur der Reihenfolge sieht das Ergebnis folgendermaßen aus:
Um die Elemente innerhalb des VStack Objekts nicht zentriert, sondern beispielsweise ganz oben oder ganz
unten anzuzeigen, muss lediglich ein Spacer() Element an der entsprechenden Stelle hinzugefügt werden.
Dieses Element nimmt den übrigen Platz ein, den die anderen Elemente nicht benötigen.
Library
Zur Vereinfachung des ganzen Prozesses stellt Xcode eine Library zur Verfügung, in der alle Views und
Modifier zusammengefasst sind. So lassen sich ganz einfach neue Elemente und Modifikationen per Drag & Drop
hinzufügen.
Preview
Innerhalb jedes View Objekts oder auch ViewController Klasse lassen sich Previews anlegen, die bestimmen,
welche und wie viele Previews angezeigt werden. So lässt sich beispielsweise eine Vorschau auf verschiedene
Geräte oder den Dark Mode erstellen. Preview bietet sehr viele und komplexe Anwendungsmöglichkeiten. In
diesem Beitrag
wird nur kurz auf die Preview eingegangen, um die Vorteile für die Entwicklung mit SwiftUI aufzuzeigen.
Im oberen Beispiel werden zwei Previews angezeigt. Die Erste ist ein PreviewLayout, für das eine feste Größe
definiert wird. Die zweite Ansicht ist ein spezifisches Gerät, das im Dark Mode angezeigt wird. Mehrere
Previews müssen in einer Group zusammengefasst werden, denn auch hier besteht der Wert Previews lediglich
aus einer View.
Die Preview Funktion ist sehr hilfreich und verhindert, dass die App immer wieder gestartet werden muss, um
das Ergebnis sehen zu können. Das ist besonders vorteilhaft für Ansichten, die in einer App nur durch viele
Klicks erreicht werden können. Außerdem bietet jede Preview einen kleinen Button, der eine Live Preview
aktiviert. Mit Hilfe dieser Funktion kann eine Ansicht interaktiv benutzt werden, ohne die App auf einem
Gerät starten zu müssen.
Beispiel einer Liste mit Custom View Elementen
Im folgenden Beispiel wollen wir eine Liste von Elementen anzeigen. Dazu legen wir zuerst eine Zelle an, die
den Inhalt sämtlicher Listenelemente bestimmt. Anschließend können wir diese Zelle in unserer ContentView
verwenden. Außerdem legen wir eine Container-Klasse an, die uns die notwendigen Daten liefern soll. Wir
beginnen mit dem Container Developer.
In dieser einfachen Klasse definieren wir ein paar Werte, die wir später befüllen und in den Views verwenden
können. Damit der Container für eine Liste in SwiftUI verwendet werden kann, muss es das Interface
Identifiable implementieren. Somit stellen wir sicher, dass jede Instanz dieser Klasse später eindeutig
identifiziert werden kann. Dafür fügen wir außerdem einen UUID-Wert hinzu, der diese Eindeutigkeit
gewährleistet. Falls wir uns im DEBUG-Modus befinden, legen wir Testdaten an, um diese direkt in der
Vorschau nutzen zu können.
Die erste View, die wir brauchen, ist eine DeveloperCell. Diese soll die Werte einer einzelnen Developer
Instanz darstellen.
Die Zelle hat eine Instanz der Developer Klasse, deren Werte sie anzeigt. Die Basis unserer View
stellt ein HStack dar, in dem Elemente horizontal angeordnet werden. Mit einem Symbol und einem Text stellen
wir ein Bild und den Namen des jeweiligen Entwicklers dar. Ist der Entwickler nicht verfügbar, färben wir
außerdem den Text sowie das Symbol grau.
Eine Bewertung stellen wir dar, indem wir das Bild eines Sterns mehrfach anzeigen, abhängig von dem
Integer-Wert rating. Dazu erstellen wir mit einem ForEach-Block ein Image mit einem Stern. Die Farbe des
Sterns ist abhängig von der Verfügbarkeit gelb oder grau. Die brightness variieren wir abhängig von der Höhe
der Bewertung. Zur kompletten Zelle fügen wir ein kleines Padding hinzu. Für die Vorschau unserer Zelle
definieren wir eine neue Instanz Developer, die wir mit Beispieldaten füllen.
Die ContentView gestalten wir so, dass diese eine Liste von Developer Instanzen erhält und deren Inhalt mit
Hilfe der erstellten Zelle anzeigt.
Wir verwenden List(developers), um für jede Instanz eine unserer Zellen anlegen zu können. Da wir eventuell
in einem weiteren Entwicklungsschritt die Daten der einzelnen Entwickler über die App anpassen möchten,
legen wir als oberstes Element eine NavigationView an, die eine NavigationBar zur Verfügung stellt und es
uns ermöglicht, von dieser Ansicht auf eine darunterliegende Ansicht zu wechseln.
Die Liste erhält einen zusätzlichen Modifier navigationBarTitle, mit dem wir den Titel der Ansicht
festlegen. Jede DeveloperCell Instanz packen wir außerdem in einen NavigationLink. Dieses Element nimmt eine
destination entgegen. Diese destination muss eine View sein, zu der nach einer Interaktion des Benutzers
navigiert werden soll. Vorerst definieren wir einfach einen Text, der den Namen des jeweiligen Entwicklers
anzeigt.
Wenn wir die App jetzt starten, sehen wir unsere Liste mit den entsprechenden Details der Entwickler. Sobald
wir eine Zelle auswählen, erscheint eine weitere Ansicht, auf der lediglich der Name des ausgewählten
Entwicklers zu sehen ist. Über den Button oben links gelangen wir auf die Übersicht zurück.
Ein großer Vorteil von SwiftUI ist das Handling der Daten. Sobald sich Werte einer Developer Instanz ändern,
wird die Ansicht aktualisiert. Darüber hinaus registriert SwiftUI, wenn Elemente hinzugefügt oder gelöscht
werden und aktualisiert die Views automatisch.
@State, @Binding und @Environment
Wenn eine View einen bestimmten Zustand definiert und kontrolliert, wird die angelegte Variable mit dem Tag
@State versehen. Ohne diesen Tag kann eine View den Wert einer Variablen nur lesen und nicht verändern.
Das Toggle, das wir definieren, verändert unsere Variable showMessage. Die Variable versehen wir mit einem
zusätzlichen $ Symbol, um ein Binding zu signalisieren (dazu gleich mehr). Je nach Zustand dieses Werts
zeigen wir einen Text an oder verstecken diesen.
Wollen wir nun eine weitere View aufrufen und dieser einen Zugriff auf showMessage ermöglichen, können wir
das mit dem @Binding Tag erreichen. Wenn wir also zum Beispiel eine weitere View anlegen, die lediglich das
Toggle beinhaltet, aber trotzdem den Status der showMessage Variable verändern können soll, ohne diese zu
besitzen, wäre das ein solcher Fall. Im folgenden Beispiel definieren wir ToggleView, die das Binding auf
showMessage enthält und verwenden diese View anschließend in der zuvor angelegten MessageView.
Wie auch schon für den Toggle übergeben wir showMessage mit einem zusätzlichen $ Symbol, das wir für ein
Binding benötigen. Das Element Toggle verwendet intern ebenfalls ein Binding auf die Variable, die übergeben
wird. Wenn wir die App nun starten, können wir mit Hilfe des Toggles die angezeigte Nachricht erscheinen und
auch wieder verschwinden lassen.
Wollen wir innerhalb einer View auf eine Umgebungsvariable zugreifen, setzen wir das mit Hilfe des
@Environment Tags um. Damit erhalten wir Zugriff auf die Statusvariablen einer View, wie zum Beispiel
isEnabled:
@Environment(\.isEnabled) private var isEnabled: Bool
Anschließend lässt sich dieser Wert für jedes Element innerhalb der View verwenden. Der Inhalt dieser
Variablen verändert sich, sobald sich der Wert der eigentlichen Umgebungsvariable ändert. Die View wird in
einem solchen Fall automatisch aktualisiert.
ZStack
Der ZStack ist ein neues Element in SwiftUI. So wie der VStack Elemente vertikal und der HStack
horizontal, ordnet ein ZStack alle Elemente übereinander an. Man verwendet dieses Element, wenn man
mehrere Views abhängig von bestimmten Bedingungen austauschen oder Ansichten wechseln möchte.