SwiftUI ist Apples neues Framework, um Benutzeroberflächen zu entwickeln. Es könnte zu diesem Zweck langfristig populärer als UIKit auf iOS und iPadOS werden. Gleiches gilt für UIKit auf tvOS, AppKit auf macOS und WatchKit für watchOS. Die erste SwiftUI-Version wurde anlässlich Apples „Worldwide Developer Conference 2019” vorgestellt. Dieses Jahr wurde zur WWDC 2020 die nächste Iteration angekündigt mit der Entwickler Apps für die neu erschienen Betriebssysteme macOS 11 und watchOS 7 sowie iOS, iPadOS und tvOS 14 entwickeln können. Ein Vorteil dieser Neuerung ist, dass Entwickler nur noch ein Framework erlernen müssen, um für all diese Plattformen UIs zu implementieren. Es lassen sich sogar Multiplattform-Apps entwickeln, die sich eine Codebasis teilen, um auf iPhones, iPads und Macs zu laufen. Dieser Blogpost ist der erste einer Reihe, die erklärt was SwiftUI ist und wie eine bestehende UIKit-basierte App zu SwiftUI migriert werden kann.

Imperative und deklarative Programmierung

Der Umstieg zu SwiftUI bringt einige neue Konzepte mit sich. Die herausstechendste Änderung ist der Wechsel des Programmierparadigmas von imperativer zu deklarativer Programmierung. Was heißt das? In der imperativen UIKit-Welt wird z. B. eine UIView mittels Befehlen zusammengebaut (z. B. addSubview(\_:), um eine UIView in eine Eltern-View einzubetten oder setImage(_ image: UIImage?, for state: UIControl.State) um das Bild eines UIButton zu ändern). Es wird also eine Handlungsabfolge programmiert, die die Nutzeroberfläche aussehen lässt, wie sie aussehen soll. Folgendes Beispiel zeigt wie ein Button, der den Dark Mode in einer App ein- und ausschalten soll, imperativ mittels UIKit implementiert werden könnte:

var settings = SettingsStore()
var darkModeButton: UIButton!

override func loadView() {
    view = UIView()

    darkModeButton = UIButton()
    darkModeButton.addTarget(self,
                             action: #selector(onDarkModeTapped),
                             for: .touchUpInside)
    updateDarkModeButton()
    view.addSubview(darkModeButton)

    // ...
}

@objc private func onDarkModeTapped() {
    settings.darkMode.toggle()
    updateDarkModeButton()
}

private func updateDarkModeButton() {
    if settings.darkMode {
        darkModeButton.setImage(UIImage(systemName: "sun.max"),
                                for: .normal)
    } else {
        darkModeButton.setImage(UIImage(systemName: "moon"),
                                for: .normal)
    }
}
Dark-Mode-Button mit UIKit

Innerhalb der loadView()-Methode eines UIViewController wird eine UIView und ein UIButton erzeugt und letzterer in die View mittels addSubview(_:) eingefügt. Wenn auf den Button getapt wird, soll sich die Einstellung invertieren. Dafür wird die darkMode-Property im SettingsStore-Model beim Aufruf der onDarkModeTapped()-Methode angepasst. Um den Button entsprechend der aktuellen Dark-Mode-Einstellung darzustellen, wird dann die updateDarkModeButton()-Methode aufgerufen und mittels setImage(_:for:) das Bild des Buttons zu einem Sonnen- oder Mond-Icon gesetzt.

Der deklarative Ansatz von SwiftUI funktioniert in dem das Aussehen der Nutzeroberfläche abhängig vom Status eines oder mehrerer Datenmodelle beschrieben wird. Folgendes Beispiel zeigt wie so etwas aussehen kann:

@ObservedObject var settings = SettingsStore()

var body: some View {
    Button(action: {
        self.settings.darkMode.toggle()
    }) {
        if self.settings.darkMode {
            Image(systemName: "sun.max")
        } else {
            Image(systemName: "moon")
        }
    }
}
Dark-Mode-Button mit SwiftUI

Abhängig vom aktuellen Status (self.settings.darkMode) wird der Button als Sonne oder als Mond dargestellt. Der @ObservedObject-Property-Wrapper ermöglicht es, dass immer, wenn sich das SettingsStore-Model verändert, die Benutzeroberfläche automatisch neu gezeichnet wird.

Die deklarative Lösung hat den Vorteil, dass sie besser lesbar ist und schnell ein gutes Verständnis von der Funktionalität des Codes erlangt werden kann. Ebenfalls ist der Code meistens kompakter als ein vergleichbarer UIKit-Ansatz.

Voraussetzungen zur Nutzung von SwiftUI

Wie können interessierte Entwickler nun SwiftUI einsetzen, um Benutzerschnittstellen zu entwickeln? Zum einen wird Apples Entwicklungsumgebung Xcode 11 oder neuer benötigt. Außerdem sollte macOS Catalina oder neuer installiert sein, damit die Vorschau-Funktion verwendet werden kann. Diese erlaubt es neben dem SwiftUI-Code eine Live-Vorschau der daraus resultierenden Benutzeroberfläche zu sehen. Schließlich muss iOS/iPadOS 13 oder neuer als Deployment-Target ausgewählt werden. Je nach App könnte dies aktuell noch ein Ausschlusskriterium sein, wenn diese auch mit älteren Geräten kompatibel sein soll. Anlässlich der WWDC im Juni 2020 veröffentlichte Apple aktuelle Zahlen zur Verbreitung der Betriebssystemversionen. So sei auf 81 % der iOS- und 73 % der iPadOS-Geräte Version 13 des Betriebssystems installiert. Betrachtet man nur die Geräte, die in den letzten vier Jahren auf den Markt kamen, liegen die Werte bei 92 % respektive 93 %.

Das einfachste Vorgehen, um direkt mit der SwiftUI-Entwicklung loszulegen, wäre natürlich ein neues Projekt anzulegen. Wie sieht es jedoch für bereits bestehende Apps aus? Auch diese können in den Genuss von SwiftUI kommen, denn es ist möglich UIKit und SwiftUI im gleichen Projekt zu benutzen und miteinander zu verknüpfen. Wie genau das geht, wird im Folgenden am Beispiel der cronn Image Recognition Playground App beschrieben. Die App ermöglicht es Fotos von verschiedenen Objekten zu sammeln und mittels dieser ein neuronales Netzwerk zu trainieren. Anschließend kann das trainierte Model genutzt werden, um durch die Kamera Eingefangenes live zu klassifizieren. Es können also z. B. einige Fotos von Hotdogs und Donuts geschossen und damit ein Klassifikator trainiert werden. Anschließend sollte die App dann live erkennen, wenn sich ein Hotdog oder Donut vor der Kamera des Geräts befindet und unterscheiden können um welches Objekt es sich handelt.

Um das Deployment-Target anzupassen, muss in Xcode im Project-Navigator das Projekt ausgewählt werden, um die Datei <Projektname>.xcodeproj zu öffnen. Anschließend muss in der linken Leiste erneut das Projekt ausgewählt werden, dann die Build Settings in der oberen Leiste ausgewählt werden und schließlich in der Kategorie Deployment das iOS Deployment Target auf iOS 13.0 oder neuer eingestellt werden.

Deployment-Target in Xcode anpassen
Deployment-Target in Xcode anpassen

Dies war der erste Teil der Blogpost-Serie zum Thema SwiftUI. Im nächsten Post wird es darum gehen wie SwiftUI-Code in ein bestehendes UIKit-Projekt integriert werden kann.