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.

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.

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)
}

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

Marcel Tuchner

ist für Sie da!

+49 441 559 770 0

Marcel Tuchner
Haben Sie Fragen?

Weitere Blogartikel

08. Jan. 2024

Persönlich nachgefragt bei Adrian Macha und Torben Schinke von worldiety

Blog

Wie schauen Adrian Macha und Torben Schinke heute auf das Projekt „worldiety Zentrum Oldenburg“? mehr

10. Jan. 2023

Generator für Softwarearchitekturen

Blog

Bei der Entwicklung von Software treten bei fortlaufender Dauer, entsprechender Größe, Komplexität und bei häufig auftretenden Änderungen Herausforderungen hinsichtlich der Architektur des zu entwickelnden Softwaresystems auf. Diese bestehen zumeist darin, den immer größer werdenden Quellcode und die zunehmende Anzahl von Softwarekomponenten passend zu organisieren. Die Architektur des Softwaresystems ist dabei maßgeblich für die Wartung und Anpassungsfähigkeit der Software als auch für die Einarbeitungszeit neuer Entwickler. mehr

01. Mai. 2022

Benutzerdokumentation automatisiert generieren

Blog

Die agile Softwareentwicklung hat sich in den letzten Jahren zu einem wichtigen Ansatz der technischen Umsetzbarkeit entfaltet. Neben den Vorteilen, wie z. B. Flexibilität, Fehlererkennung und erhöhte Performanz durch eine stetige Kommunikation, bringt eine agile Softwareentwicklung jedoch auch Einschränkungen mit sich. So wird die Dokumentation - zu welcher auch die Benutzerdokumentation zählt - eher relativiert betrachtet und zugunsten der engen Zusammenarbeit zwischen Entwickler:innen, Tester:innen, Kund:innen und Nutzer:innen auf ein Minimum beschränkt. Bedingt durch Covid-19 musste der persönliche Kontakt mit Kunden, welcher in einer agilen Entwicklungsumgebung einen hohen Stellenwert besitzt, auf ein Minimum reduziert werden. Dabei gewann Software allgemein in den letzten Jahren immer mehr an Komplexität, welches auch eine zunehmende Rolle in der Organisation von Informationen innerhalb der Benutzerdokumentation zur Folge hat. mehr

28. Apr. 2022

Empathy Maps als UX-Tool

Headerbild Empathy Maps
Blog

In Entwicklungs-, Design- oder Marketing-Teams bestehen oftmals unterschiedliche Vorstellungen von Zielgruppen, bzw. dem Endnutzer einer Applikation. Dies kann dann problematisch werden, wenn bspw. neue Features geplant oder versucht wird, den Endnutzer in Texten sowie Bildern direkt anzusprechen. Vor allem aber führt dies oftmals zu langwierigen Prozessen sowie Entscheidungen über die Nutzer und deren Bedürfnisse. Um dieser Herausforderung entgegenzuwirken, lassen sich unterschiedliche Ansätze sowie Methoden nutzen. Eine besonders effiziente und in der Umsetzung einfache Methode ist die „Empathy Map“. Empathy Maps sind ein agiles Tool im Bereich des User Experience Designs, das dabei hilft, die Nutzer sowie deren Bedürfnisse besser zu verstehen und ein einheitliches Mindset im Projekt-Team zu etablieren. Die Nielsen Norman Group, eine Erfolgreiche UX Beratungsfirma aus Amerika, welche von den User Experience Pionieren, Don Norman und Jakob Nielsen gegründet wurde, definiert Empathy Maps wie folgt: mehr