Ergänzen fehlender Xcode-Funktionen - Teil 2

19. Oktober 2016

In unserem letzten Beitrag haben wir bereits über die fehlende Aktualisierung von Übersetzungen und mögliche Lösungsansätze berichtet. Diese Woche geht es weiter mit unserer Einführung in fehlende Funktionen in Xcode und wie man diese umgehen kann. Im nächsten Teil erwartet dich dann eine Erklärung zu fehlenden Code Conventions und TODO-Warnungen.

Dynamisch referenzierte Ressourcen

Problem

Wie im letzten Teil bereits erläutert, bietet die Foundation Library das Makro NSLocalizedString(key, comment) an, um Texte in Xcode-Projekten zu übersetzen. Der key ist dabei vom Typ String, was den großen Nachteil hat, dass alle Automatismen von statischer Typisierung beim Builden von Code nicht mehr greifen. Das bedeutet konkret, dass Xcode nicht überprüfen kann, ob zu den Werten, die als Keys im Code genutzt werden, auch tatsächlich Übersetzungen existieren. So kann ein Tippfehler, eine Änderung des Keys im Code oder fehlende Übersetzungen zu neuen Keys zu dem Problem führen, dass am Ende leere Texte in der App angezeigt werden und dies beim Entwickeln und Builden gar nicht auffällt.

Gleiches gilt auch für Bilder, die mit UIImage(named:) durch einen String initialisiert werden. Xcode durchsucht dann automatisch sowohl die .xcassets-Ordner des Projekts, als auch alle direkt hinzugefügten Bilder und gleicht deren Namen mit dem übergebenen String ab, um so das korrekte Bild zu finden. Interfaces lädt man auf ähnliche Weise mittels Strings von Storyboards oder XIBs, Farben werden meist mit RGB-Werten bei Notwendigkeit ad-hoc angelegt. Analog zu den dynamisch referenzierten Übersetzungen verhält es sich auch bei per Strings referenzierten Bildern oder Interfaces. Tippfehler oder Umbenennungen im Code oder den jeweiligen Ressourcen führen dazu, dass beim Ausführen der App Bilder fehlen, Interfaces nicht gefunden werden können oder die Farben an vielen unterschiedlichen Stellen gesucht und geändert werden müssen. Xcode zeigt in Fehlerfällen weder Warnungen noch Fehlermeldungen an, da es die tatsächlich existierenden Ressourcen schlichtweg nicht kennt, weil sie dynamisch mit den Strings oder den RGB-Werten geladen werden.

Übersetzungen, Bilder, Interfaces und Farben seien im Folgenden unter "Ressourcen" zusammen gefasst.

Lösungsvorschlag

Ressourcen sollten statisch über automatisch generierten Code gepflegt und geladen werden, sodass man einerseits gar nicht mehr mit fehlenden Ressourcen builden kann (Xcode zeigt Fehlermeldungen an, wenn referenzierter Code nicht vorhanden ist) und andererseits eine zentrale Stelle zum Verwalten und Ändern hat. Letzteres ist vor allem bei den Farben wichtig, die in den meisten Apps einheitlich sein sollten. Das Tool SwiftGen leistet hierbei hervorragende Dienste und durchsucht das Projekt automatisch nach Übersetzungen, Bildern, Interfaces und Farben.

Installieren lässt sich SwiftGen am einfachsten mit dem Befehl brew install swiftgen, vorausgesetzt Homebrew ist installiert. Die Konfiguration in einem Projekt geschieht analog zu BartyCrouch mithilfe eines Build Scriptes, in welchem man die verschiedenen Befehle und Pfade, die man nutzen möchte angibt. Im Folgenden werden die einzelnen Funktionen erläutert, am Ende folgt ein Beispiel-Build-Script.

Übersetzungen

Was Übersetzungen betrifft untersucht SwiftGen automatisch alle .strings-Dateien auf ihre Keys und generiert aus diesen eine Code-Struktur namens Strings.swift worin sämtliche Keys per Dot-Syntax erreichbar sind und als Ergebnis die jeweilige Übersetzung liefern. Dies ermöglicht es alle Aufrufe von NSLocalizedString("SETTINGS.NAVIGATION_BAR.TITLE", comment) durch Aufrufe wie L10n.NavigationBar.Title zu ersetzen. Dies löst zum Einen das Problem der statischen Überprüfung, da Xcode bereits automatisch Code auf Existenz prüft, wenn er kompiliert wird und bietet als Nebeneffekt auch den Vorteil, dass man für sämtliche existierende Keys nun auch die Autovervollständigung nutzen kann, was bei reinen Strings nicht möglich wäre.

Zusätzlich zur Verwendung von SwiftGen sei an dieser Stelle noch eine Übersetzungs-Key-Struktur dabei empfohlen, die die Autovervollständigungsfunktion maximal ausreizt, Übersetzungskommentare als Kontext-Informanten überflüssig macht und das Unterscheiden von Übersetzungs-Keys und tatsächlichen Übersetzungen vereinfacht. Die Struktur sollte folgende Regeln einhalten:

  • Die Keys werden KOMPLETT IN GROSSBUCHSTABEN GESCHRIEBEN
  • Die Keys werden hierarchisch.mit.Punkten.getrennt
  • Trennungen zwischen Wörtern in den Keys werden mit_Unterstrichen_umgesetzt

Nachfolgend sind ein paar Beispiel-Keys aufgelistet, die diese Vorgaben einhalten:

SETTINGS.TITLE
SETTINGS.FONT_SECTION.SIZE
SETTINGS.FONT_SECTION.COLOR
MODEL.ARTICLE.TITLE
MODEL.ARTICLE.RELEASE_DATE

Mit SwiftGen können sie dann folgendermaßen verwendet werden:

self.title = L10n.Settings.Title
articleCell.titleLabel.text = L10n.Model.Article.Title

Farben

Zur Verwendung der Farbenfunktionalität sollte eine Colors.txt-Datei im Hauptverzeichnis des Projekts erstellt werden, deren Inhalt etwa folgendermaßen aussehen kann:

Primary     : #40657d
Secondary   : #657d40
Accent      : #b7d3e3

Die Benutzung der konfigurierten Farben sieht dann etwa so aus:

self.view.backgroundColor = UIColor(named: .Primary)
self.view.tintColor = UIColor(named: .Accent)

Bilder & Interfaces

Zur Nutzung der Bilder- und Interfaces-Funktionen ist keine spezielle Konfiguration vonnöten. Die Nutzung ist auch einfach:

let image = UIImage(asset: .Banana)
let wizardViewCtrl = StoryboardScene.Wizard.initialViewController()

Build Script

Das kombinierte Build Script, um alle obigen Funktionen zu nutzen, sieht wie folgt aus (auf Wunsch sind einzelne Zeilen entfernbar):

if which swiftgen > /dev/null; then
    swiftgen strings "$PROJECT_DIR/Sources" -t dot-syntax --output "$PROJECT_DIR/Sources/Constants/Strings.swift"
    swiftgen storyboards "$PROJECT_DIR/Sources" --output "$PROJECT_DIR/Sources/Constants/Storyboards.swift"
    swiftgen images "$PROJECT_DIR/Sources" --output "$PROJECT_DIR/Sources/Constants/Images.swift"
    swiftgen colors "$PROJECT_DIR/Colors.txt" --output "$PROJECT_DIR/Sources/Constants/Colors.swift"
else
    echo "warning: SwiftGen not installed, download it from https://github.com/AliSoftware/SwiftGen"
fi

Hinweis: Man beachte, dass beim Einfügen des Scripts in das Build-Script-Feld in Xcode die Formatierung des Textes verloren geht. Diese lässt sich mit ein paar Einrückungen jedoch schnell wieder herstellen, um den Code im Script lesbarer zu halten.

Die Ergebnis-Dateien werden im Ordner Sources/Constants abgelegt, welcher vorher erstellt werden sollte. Fortan werden die Dateien bei jedem Build neu erzeugt, sofern es seit der letzten Generierung Änderungen an den jeweiligen Ressourcen gab.