Code-Beispiel

Nachhaltige Modularisierung in Swift

22. Jul. 2020

Verwendung von dem Swift Package Manager mit SwiftUI und Combine

Die Vorteile von Modularisierung sind allseits bekannt. Durch die Aufteilung einer großen Einheit in einzelne Module können Komponenten beispielsweise erneut verwendet werden, wodurch bei UI-Komponenten sowohl die Arbeitszeit reduziert als auch das Design vereinheitlicht werden kann. Darüber hinaus erleichtert eine logische Trennung einzelner Komponenten die Einarbeitung in eine neue Projektstruktur und führt dazu, dass einzelne Komponenten einfacher getestet werden können. Letzteres ist insbesondere in der Softwareentwicklung von bedeutender Relevanz.

Doch wie kann ein theoretisches Konzept der Modularisierung in der Praxis, genauer gesagt in der Programmiersprache Swift für iOS-Applikationen umgesetzt werden?

Wir haben’s ausprobiert – anhand einer selbst entwickelten iOS Wetter-App. Die hierfür verwendeten Wetterdaten werden von der OpenWeatherMap API angefragt, aufbereitet und anschließend dargestellt.

Projektstruktur mit Swift UI: Deklarativ und innovativ

Xcode Workspace mit Swift Package Manager

Um das UI zu erstellen, wurde sich für die auf der WWDC 2019 vorgestellte Technologie SwiftUI entschieden. Hierbei handelt es sich um einen deklarativen Ansatz, der UI Zustände in Swift beschreibt. Das Layouting wird von der SwiftUI Rendering Engine übernommen. In der Geschäftslogik wird das Combine Framework verwendet, welches einen reaktiven Programmieransatz verwendet und sich sehr gut in SwiftUI integrieren lässt. In dieser Kombination lassen sich mit SwiftUI native Anwendungen für IOS, macOS, watchOS und tvOS einfach und effektiv programmieren.

Nach der Erstellung eines neuen SwiftUI Projekts, erhält man zunächst folgende Ansicht:

Zur Verbesserung der Projektstruktur, wird im nächsten Schritt ein Workspace erstellt und das bisherige Projekt hinzugefügt. Dies hat den Vorteil, dass wir sowohl das Projekt als auch alle benötigten Module in einem Workspace bearbeiten können. Um die Geschäftslogik und die UI-Komponenten zu definieren, muss anschließend das Swift Package „WDYWeatherCore“ geschaffen werden. In diesem Package finden sich wichtige Ordner für die zwei Module „WDYWeatherCore“ und „WDYWeatherViews“, wodurch folgende Projektstruktur entsteht:

Danach müssen die Module „WDYWeatherCore“ und „WDYWeatherViews“ noch als Frameworks im „WDYWeather“ Target hinzugefügt werden, sodass eine Projektstruktur entsteht, die in der weiteren Entwicklung viele Vorteile hat:

Zum einen ist die „Hürde“ für die Erstellung von weiteren Modulen nun sehr gering, sodass eine logische Gliederung sowie die Wiederverwendung von Modulen einfach und mit wenig Aufwand möglich sind. Diese Wiederverwendbarkeit ist dann zwingend erforderlich, wenn es sich um eine iOS Share Extension oder macOS, watchOS, tvOS App handelt, da diese in Xcode als neue „Targets“ anzusehen sind und dadurch eine eigene Verzeichnisstruktur besitzen. Zum anderen können einzelne Module nun problemlos größer und ggf. in ein eigenständiges Paket ausgelagert werden. So ist auch eine Wiederverwendbarkeit bei weiteren Projekten möglich.

API Anfrage mit Combine

Das „WDYWeatherCore“ Modul enthält die Geschäftslogik der App. Sie besteht in diesem Projekt aus der Abfrage und Aufbereitung von Wetterdaten. Hierfür werden Swift Structures erstellt, welche die Rückgabedaten der API repräsentieren. Sie können mit Hilfe der Web-App unkompliziert aus den JSON Wetterdaten der API generiert werden. Um zwischen den Modulen eine logische Trennung zu erhalten, werden diese Datentypen mit einem Access Level internal deklariert. Dies bedeutet, dass die Daten der API innerhalb des „WDYWeatherCore“ Moduls verarbeitet und aufbereitet werden sollen. Als Schnittstelle für die App wird die „WeatherModel“ Structure erstellt. Sie enthält alle wichtigen Daten, die in der App benötigt werden.

WeatherModel.swift

public struct WeatherModel {
    public let location: String
    public let current: Datapoint?
    public let forecast:[Datapoint]
}

Ein Datenpunkt besteht hierbei aus einem Datum, der Temperatur, dem Luftdruck, der Luftfeuchtigkeit und einem Enum, dass eine Zusammenfassung des Wetters enthält wie z.B. „sonnig“, „bewölkt“, „regnerisch“, „stürmisch“. Diese Zusammenfassung wird beispielsweise für die Darstellung der Wetter-Symbole verwendet.

Für die Anfrage der Wetterdaten kommt das Combine Framework zum Einsatz. Wir erstellen jeweils einen Publisher, der die aktuellen Wetterdaten und die Wettervorschau bei der API anfragt. Nun kann eines der Kernfeature von Combine genutzt werden. Es soll erst dann eine Rückgabe erfolgen, wenn beide Publisher ein Ergebnis geliefert haben und diese Ergebnisse aufbereitet wurden. Anstatt hier klassisch mit beispielsweise einer DispatchSemaphore zu arbeiten, kann der soeben beschriebene Zusammenhang mit dem Combine Framework reaktiv ausdrückt werden.

WeatherAPI.swift

public func getData(locationName: String = "Oldenburg") -> AnyPublisher<WeatherModel, APIError> {
    return getForecastData(locationName: locationName)
        // 1
        .zip(getCurrentData(locationName: locationName))
        // 2
        .tryMap { (forecast, current) in
            try self.mapToWeatherModel(forecast, current, for: locationName)
        }
        // 3
        .mapError { inputError in
        if let inputError = (inputError as? APIError) {
            return inputError
        } else {
            return .network(description: inputError.localizedDescription)
        }
        // 4
        .eraseToAnyPublisher()
}

Zunächst (1) kombinieren wir die Ergebnisse der beiden API Publisher. Anschließend (2) wird aus den Daten das „WeatherModel“ erstellt. Es folgt (3) eine Fehlerbehandlung möglicher fehlerbehafteter Antworten der API. Abschließend (4) ist es bei der Nutzung des Combine Frameworks üblich, das als Publisher Datentyp ein „AnyPublisher<Output, Failure>“ zurückgegeben wird.

Das dadurch erzeugte „WeatherModel“ enthält nun alle relevanten Daten für unser UI. Bei der Implementierung ist insbesondere auf die Modularisierung zu achten, sodass keine Abhängigkeiten zu einem UI Framework wie beispielsweise AppKit, UIKit oder SwiftUI erzeugt werden. Dadurch lässt sich diese Geschäftslogik nicht nur in einer iOS App, sondern auch in einer nativen macOS App verwenden.

Modularisierung von Teilen des UIs

SwiftUI Komponenten in einem Swift Package

Für die Implementierung einer iOS App mit SwiftUI bietet sich die Verwendung eines Model View ViewModel (MVVM) Ansatzes an. Dadurch, dass die App eine Liste der Wettervorschaudaten enthalten soll, kann diese Liste möglicherweise auch in einer watchOS App verwendet werden. Um dies zu ermöglichen, muss das Zellendesign in das „WDYWeatherViews“ Modul integriert werden – dies gelingt wie folgt:

Zunächst wird das „ForecastItemViewModel“, welches alle anzuzeigenden Daten enthält, erstellt. Die Aufbereitung der einzelnen Daten findet hierbei in dem ViewModel statt, so wird z.B. der Zeitstempel in das gewünschte Format konvertiert. Anschließend wird die „ForecastItemView“ erstellt, welche das ViewModel enthält. Dort können mit Hilfe von SwiftUI die Texte und Bilder ausgerichtet werden. Die daraus entstandene Ansicht kann mit Hilfe eines „PreviewProviders“ angesehen und ggf. feinjustiert werden.

Zusammenführung der Geschäftslogik und UI-Komponenten

WDY Weather für iOS

Nachdem die Geschäftslogik und die UI-Komponenten bisher getrennt voneinander entwickelt wurden und keinerlei Berührungspunkte hatten, kann nun die Implementierung der iOS App erfolgen. Hierfür wird unser zuvor angelegtes Projekt aufgerufen und die „WeatherView“ erstellt. Sie enthält alle wichtigen Komponenten.

Neben der deklarativen Herangehensweise bietet SwiftUI noch weitere Vorteile bei der Entwicklung, die für eine enorme Zeitersparnis sorgen. So kann bspw. eine Vorschau der zu bearbeitenden Ansicht live in der Entwicklungsumgebung Xcode angezeigt werden. Anpassungen an dem UI können dadurch einfach und effizient vorgenommen werden, ohne dass ein Entwickler die App neu öffnen und in die bearbeitete Ansicht navigieren muss.

WeatherView.swift

private var forecastView: some View {
    List(viewModel.forecastItems) { item in
        ForecastItemView(viewModel: item)
    }
    .listSeparatorStyleNone()
    .frame(maxWidth: 500, maxHeight: 300, alignment: .center)   // set maxWidth for iPad
    .cornerRadius(8)
}

Die „forecastView“ Variable enthält hierbei die zuvor erzeugte „ForcastItemView“ Komponente aus dem „WDYWeatherViews“ Modul.

Für die Anpassung an weitere Apple-Geräte, wie z.B. das iPad, musste in diesem simplen UI lediglich die Breite der Vorschau-Liste auf einen maximalen Wert von 500 Punkten begrenzt werden. So erhalten wir eine voll funktionsfähige Wetter-App, die für alle mobilen Endgeräte-Größen die verfügbaren Wetterdaten über die API anfragt.

Deklarative UI-Entwicklung und Modularisierung in Swift

Ein UI-Framework mit Zukunftspotenzial

Mithilfe der durch den Swift Package Manager eingeführten Paketstruktur und der Integration in Xcode lässt sich eine logische Zerteilung von Komponenten in Swift realisieren. Sind die Grundlagen in einer Projektstruktur erst einmal geschaffen, können weitere Module einfach ergänzt werden.

Die beschriebenen Vorteile sind offensichtlich: Durch die Entwicklung in Komponenten wird schon bei der Implementierung der Fokus auf die Wiederverwendbarkeit gelegt. Durch eine klare Projektstruktur finden sich nicht nur neue Kollegen schneller im Projekt zurecht, auch profitiert die Arbeit am Projekt durch kürzere Compile-Zeiten.

Wir haben gesehen, dass mit SwiftUI auch UI-Komponenten in einzelne Module verpackt werden können. So kann bspw. ein Corporate Design auch in einer Codebasis aufgebaut werden. Einmal entwickelte und designte UI-Komponenten lassen sich anschließend beliebig erneut verwenden. Trotz Wiederverwendbarkeit gibt es bei der aktuellen Version von Swift (5.2) allerdings noch ein Hemmnis, denn: Aktuell ist es noch nicht möglich weitere Ressourcen wie bspw. xib Dateien, Storyboards oder auch Bilder über ein Modul bereitzustellen. Hierfür wurden allerdings bei der Swift Evolution bereits die Proposals 0271 und 0278 eingereicht und implementiert, sodass diese Einschränkung mit Swift 5.3 aufgehoben wird.

Um die Verwendung der erstellten Komponenten auch anhand anderer Systeme zu verdeutlichen, werden wir in den folgenden Blogartikeln näher auf die Erstellung von Apps für macOS, watchOS und tvOS eingehen.

Marcel Tuchner

Unser Swift-Experte

ist für Sie da!

Marcel Tuchner
Haben Sie Fragen?

Bilder aufeinander
Allgemein

16. Jul. 2021

Mit einem automatischen Layout-Algorithmus zu mehr Ästhetik

Der Trend, Erinnerungen und Momente in Form von Fotos oder Videos festzuhalten, ist in den letzten Jahren stetig gestiegen. Doch trotz der steigenden Vernetzung und der Möglichkeit, Fotos digital abzurufen und sie sich auf seinem Endgerät anzeigen zu lassen, haben personalisierte Fotobücher in analoger Form ihren Charme nicht verloren. mehr

Headerbild Team
Employer Branding

01. Feb. 2021

Employer Branding at its best

370 Tage mit Corona, 233 Tage im Home-Office, 0 vor Ort stattgefundene Team-Events – das letzte Jahr war für uns alle in jeglicher Hinsicht eine Herausforderung und hat dabei nicht nur unser Privat-, sondern vor allem auch unser Arbeitsleben grundlegend verändert. mehr

Logo MobileHCI 2020
User Experience

20. Okt. 2020

The importance of User Experience

The ACM Conference on Human-Computer-Interaction with Mobile Devices and Services, better known as the MobileHCI, is generally regarded as the leading academic conference in the field of mobile human-computer-interaction. Due to the current COVID-19 situation, the MobileHCI 2020, planned in Oldenburg, now was completely virtually. Torben Schinke, CEO of worldiety, was one of the sponsors and has been interviewed by Anne Joutsenvirta, who represents the Industry Perspective Chairs. mehr

Schreibtisch mit digitalen Tools
Digitalisierung

22. Sep. 2020

Mit diesen Tools zu digitalisierten Arbeitsprozessen

Die aktuelle Situation zeigt deutlich, wie wichtig digitalisierte Prozesse sind und warum es für alle Unternehmensbereiche unabdingbar ist, sich zu digitalisieren. Doch was bedeutet es überhaupt sich zu digitalisieren und Unternehmensstrukturen- sowie Prozesse zu verbessern und wettbewerbsfähig zu bleiben? Worauf ist zu achten und welche Möglichkeiten der Digitalisierung bestehen? Die nachfolgenden Top 3 Tools zeigen, wie effektiv und effizient Arbeitsprozesse digital gestaltet werden können: mehr