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?

Weitere Blogartikel

23. Jun. 2022

Simulierte Netzwerktests

Blog

Mobiles Internet für alle – vor 20 Jahren noch eine weit entfernte Vision, heute bereits Realität. 86% der Deutschen besitzen laut einer aktuellen [Statistik](https://de.statista.com/statistik/daten/studie/585883/umfrage/anteil-der-smartphone-nutzer-in-deutschland/) heutzutage bereits ein internetfähiges Smartphone, Tendenz steigend. Gleichzeitig bildet Deutschland jedoch das Schlusslicht Europas wenn es um das Thema Netzausbau geht – nur 65% des Landes wird mit einem 4G Netz abgedeckt, 2% weniger als in Albanien und 24% hinter den Niederlanden, siehe aktuelle [Analysen](https://www.opensignal.com/reports/2018/02/state-of-lte). Diese Diskrepanz führt zu einem beinnahe alltäglichen Problem in der mobilen Anwendungsentwicklung. Nahezu jede Anwendung besitzt heutzutage Funktionalitäten, die eine Internetverbindung benötigen. Dabei gehört es grundsätzlich zu den Anforderungen an ein Softwaresystem, dass dieses unter allen sich ihm bietenden Netzwerkbedingungen stabil, oder zumindest vorhersagbar agiert. 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

15. Apr. 2022

Flexibel einsetzbare Markupsprache

Blog

Die Idee, dass Daten wertvoll sind und das strukturierte Speichern dieser sinnvoll ist, wurde schon in den 60er Jahren im Konzept des Generic Coding erkannt. Diese Versuche, eine vereinheitlichte Sprache zur Beschreibung von Daten zu entwickeln, mündeten 1986 in die Entstehung der Standard Generalized Markup Language (SGML), welche sich durch die Verwendung von sogenannten Tags auszeichnet. Die Ähnlichkeit zu modernen Markup-Sprachen wie HTML oder XML ist kein Zufall, da diese SGML-konform entstanden sind, sich aber mittlerweile davon gelöst haben, um ihre Struktur weniger eingeschränkt anpassen zu können. mehr