Eines der Highlights der diesjährigen WWDC war definitiv die Vorstellung der neuen Version 2 von watchOS, dem Betriebssystem der Apple Watch. Zusammen mit watchOS 2 stellte Apple für uns Entwickler auch jede Menge neue APIs und Frameworks vor, die es uns erlauben, unsere Apple Watch-Apps auszubauen und ganz neue Funktionen zu integrieren. Eine dieser Funktionen ist die Erstellung eigener sogenannter Complications.

Complications (zu Deutsch: Komplikationen) bezeichnen Zusatzfunktionen einer Uhr, die diese neben dem reinen Anzeigen der Zeit zur Verfügung stellt (beispielsweise eine Datumsanzeige oder eine Stoppuhr). Apple hat diesen Begriff auch auf die Apple Watch übertragen und bezeichnet dort ebenfalls alle Funktionen auf einem Ziffernblatt, die nichts mit der Uhrzeitanzeige zu tun haben, als Complication. Mit der Vorstellung von watchOS 2 auf der diesjährigen WWDC präsentierte Apple auch das neue ClockKit Framework, welches es uns erlaubt, eben solche Complications selbst zusammen mit unseren Apple Watch-Apps zu entwickeln und anzubieten.

Complication-Templates

Complications auf der Apple Watch werden in den verschiedensten Größen und Formen angezeigt. Wo bei manchen gerade einmal Platz für eine zweistellige Zahl ist, passt bei anderen ein mehrzeiliger Text hinein. Um diesen Umstand und diesen unterschiedlichen Anforderungen an Complications gerecht zu werden, unterscheidet Apple zwischen verschiedenen sogenannten Complication-Templates. Ein solches Template identifiziert eindeutig die Größe und das Aussehen einer Complication und wir können und müssen explizit festlegen, welche Templates wir unterstützen und welche nicht. Mit watchOS 2 stehen uns die folgenden Templates zur Verfügung:

  • Modular Small: Beschreibt kleine viereckige Complications in Digitalanzeigen
  • Modular Large: Beschreibt große rechteckige Complications in Digitalanzeigen
  • Utilitarian Small: Bezeichnet kleine rechteckige Complications in Analoganzeigen
  • Utilitarian Large: Bezeichnet große rechteckige Complications in Analoganzeigen
  • Circular Small: Bezeichnet kleine runde Complications in Analoganzeigen

Wir selbst legen in den Target-Einstellungen unserer WatchKit Extension fest, welche dieser Templates wir unterstützen. Dazu bringt Xcode 7 einen neuen Abschnitt namens Complications Configuration in den General-Einstellungen mit, der für jedes der verfügbaren Templates eine Checkbox vorhält.

Die unterstützten Templates der eigenen Complications werden über den neuen Abschnitt "Complications Configuration" der WatchKit Extension in Xcode 7 festgelegt.
Die unterstützten Templates der eigenen Complications werden über den neuen Abschnitt „Complications Configuration“ der WatchKit Extension in Xcode 7 festgelegt.

Neben der Festlegung der Templates, die unsere App unterstützen soll, findet sich auch ein Textfeld mit dem Titel Data Source Class. Wie der Name bereits andeutet, muss hier der Name der Klasse angegeben werden, über die watchOS die Informationen zu unserer Complication erhält. Aus diesem Grund liefert das ClockKit Framework ein neues Protokoll mit dem Namen CLKComplicationDataSource. Unsere Aufgabe besteht darin, eine eigene Klasse zu erstellen, die konform zu diesem Protokoll ist und in der wir alle benötigten Methoden implementieren. Eben diese Klasse muss dann innerhalb des Textfelds Data Source Class hinterlegt werden. Die grundlegende Konfiguration zur Bereitstellung von Complications in eigenen Apple Watch-Apps ist damit abgeschlossen. Im nächsten Schritt geht es an den Code.

Template-Klassen

Bevor wir uns die Arbeit mit und die Methoden des CLKComplicationDataSource-Protokolls im Detail ansehen, möchte ich zuvor eine ganz zentrale Frage klären: Wie erstelle ich überhaupt eine Complication im Code?

Die Erstellung von Complications erfolgt mithilfe von Subklassen der Klasse CLKComplicationTemplate. Diese Klasse steht an oberster Stelle aller Complications die wir erstellen und anzeigen. Sie ist letzten Endes das Objekt, das auf dem Ziffernblatt als Complication angezeigt wird.

Die Besonderheit bei der Erstellung von Complications besteht aber darin, dass wir nicht direkt Instanzen der Klasse CLKComplicationTemplate erzeugen, sondern stattdessen nur Objekte von zur Verfügung stehenden Subklassen erstellen. Jede dieser Subklassen enthält dabei zwei essenzielle Informationen:

  1. Für welche Art von Template kann diese Subklasse ausschließlich verwendet werden?
  2. Wie sieht die entsprechende Complication aus und wie ist sie aufgebaut?

Um etwas konkreter zu werden sei an dieser Stelle ein kleines Beispiel genannt. Um eigene Complications für das Template Modular Large anzubieten und zu erstellen, stehen insgesamt vier separate Subklassen von CLKComplicationTemplate zur Verfügung. Möchten wir also eine Complication für dieses Template erstellen, müssen wir entsprechend auch eine Instanz von einer dieser Klassen erzeugen. Welche wir dabei wählen, entscheidet darüber, wie die Complication aussieht und aufgebaut ist. Im Folgenden sind alle zur Verfügung stehenden CLKComplicationTemplate-Subklassen für das Modular Large-Template aufgeführt und deren Aufbau und Aussehen beschrieben:

  • CLKComplicationTemplateModularLargeColumns: Dient zur Darstellung von Text, der in drei Reihen und zwei Spalten aufgeteilt ist. Alternativ kann auch eine zusätzliche Spalte zu Beginn ganz links mit einer Grafik mit angezeigt werden.
  • CLKComplicationTemplateModularLargeStandardBody: Dient zur Darstellung von Text über drei Zeilen verteilt, wobei die erste Zeile als Header passend formatiert wird. Alternativ kann dem Header eine zusätzliche Grafik vorangestellt werden.
  • CLKComplicationTemplateModularLargeTable: Dient zur Darstellung von Text, der in zwei Zeilen und zwei Spalten aufgeteilt ist. Darüber wird ein passend formatierter Header angezeigt, dem alternativ eine zusätzliche Grafik vorangestellt werden kann.
  • CLKComplicationTemplateModularLargeTallBody: Dient zur Darstellung eines groß formatierten Texts und eines darüber liegenden Headers.

Je nach Wahl der zu verwendenden Klasse für eine Complication stehen verschiedene Properties zur Verfügung, die wir für die Konfiguration ansprechen können. So verfügt beispielsweise die Klasse CLKComplicationTemplateModularLargeTable über Properties wie row1Column1TextProvider oder row2Column1TextProvider, um darüber auf die zur Verfügung stehenden Reihen und Spalten dieses Tabellen-Layouts zuzugreifen und ihnen Text zuzuweisen, während die Klasse CLKComplicationTemplateModularLargeTallBody lediglich über zwei Properties – nämlich headerTextProvider und bodyTextProvider – verfügt, um einen Titel und einen Text für die Complication zu setzen. Das verdeutlicht, wie wichtig die Wahl der CLKComplicationTemplate-Subklasse zur Erstellung und Konfiguration eigener Complications ist, denn eben diese Subklasse legt die genaue Verfügbarkeit (welches Template wird unterstützt?) sowie den Aufbau und das Aussehen der Complication fest.

Text- und Image-Provider

Ist die Wahl getroffen und die gewünschte CLKComplicationTemplate-Subklasse zur Erstellung der eigenen Complication gefunden, geht es im nächsten Schritt an die eigentliche Erstellung und Konfiguration einer passenden Instanz. Hier wartet sodann die nächste Besonderheit des ClockKit Frameworks auf uns. Diese betrifft den Umgang mit Texten und Bildern, denn das sind die einzigen Elemente, die einer Complication – abhängig von der gewählten CLKComplicationTemplate-Subklasse – zugewiesen werden können. Statt hier bekannte Klassen wie NSString oder UIImage für die Bereitstellung der entsprechenden Elemente zu verwenden, bietet das ClockKit Framework eigene Klassen, die derartige Objekte in einer Complication abbilden. Namentlich handelt es sich dabei um die Klassen CLKTextProvider und CLKImageProvider. Betrachten wir zunächst einmal die Erstellung von Texten für eine Complication mithilfe der Klasse CLKTextProvider.

Ähnlich wie die Klasse CLKComplicationTemplate werden auch von der Klasse CLKTextProvider selbst keine Instanzen erzeugt. Stattdessen finden sich im ClockKit Framework drei Subklassen von CLKTextProvider, die zur Erstellung eines entsprechenden Text-Objekts herangezogen werden können. Diese sind:

  • CLKSimpleTextProvider: Wird zur Erstellung eines einfachen Textes verwendet.
  • CLKTimeIntervalTextProvider: Wird zur Darstellung eines Zeitintervalls mit Start- und Enddatum verwendet.
  • CLKTimeTextProvider: Wird zur Anzeige eines spezifischen Datums / einer spezifischen Uhrzeit verwendet.

Je nachdem, welche „Art“ von Text man also anzeigen möchte, wird eine der drei genannten Subklassen zur Erstellung eines entsprechenden Text-Objekts verwendet. Jede der Klassen bringt dazu passende Eigenschaften, Methoden und Initializer mit. Einen einfachen Text mithilfe der Klasse CLKSimpleTextProvider kann beispielsweise auf die im folgenden Listing gezeigte Art und Weise erstellt werden:

Für Bilder steht äquivalent – wie bereits geschrieben – die Klasse CLKImageProvider zur Verfügung. Diese kann direkt zur Erzeugung passender Objekte verwendet werden, ohne auf zusätzliche Subklassen angewiesen zu sein. Dazu bringt diese Klassen alle notwendigen Initializer und Eigenschaften mit. Das folgende Listing zeigt die einfache Erstellung einer eigenen CLKImageProvider-Instanz:

Mit diesem Wissen über Text- und Image-Provider gewappnet, kann man sich nun die Erstellung einer spezifischen Complication einmal im Detail ansehen.

Erstellen einer Complication

Wir wissen inzwischen, dass sich eine Complication aus zwei Bausteinen zusammensetzt: Zunächst wäre da eine CLKComplicationTemplate-Subklasse, die bestimmt, für welche Ziffernblätter und für welche Größen die Complication zur Verfügung steht. Darüber hinaus definiert diese Subklasse bereits den Aufbau und das Aussehen der Complication.

Um die gewählte Art von Complication dann mit Leben zu füllen, müssen Objekte der Klassen CLKTextProvider (für Text) und / oder CLKImageProvider (für Bilder) erstellt und den passenden Properties der gewählten CLKComplicationTemplate-Subklasse zugewiesen werden. Welche Properties zur Konfiguration der Complication dabei zur Verfügung stehen, hängt einzig und allein von der gewählten CLKComplicationTemplate-Subklasse ab.

Betrachten wir zur Verdeutlichung des Erstellen einer Complication einmal ein konkretes Beispiel, welches so auch auf alle anderen zur Verfügung stehenden CLKComplicationTemplate -Subklassen übertragen werden kann. Dazu soll der Einfachheit halber eine Complication der bereits zuvor vorgestellten Klasse CLKComplicationTemplateModularLargeTallBody erstellt und konfiguriert werden. Mit dieser Klasse definieren wir eine große rechteckige Complication, die nur für Digitalanzeigen zur Verfügung steht. Diese Complication-Klasse kann nur Text anzeigen und verfügt dazu über zwei passende Properties: headerTextProvider und bodyTextProvider. Das folgende Listing zeigt nun einmal die komplette Erstellung einer Complication inklusive Konfiguration der zur Verfügung stehenden Properties:

Fertig! Mit dem Erzeugen eines Objekts der gewünschten CLKComplicationTemplate-Subklasse und dem anschließenden Setzen der passenden Werte für die zur Verfügung stehenden Properties ist die Erstellung einer Complication abgeschlossen!

Das CLKComplicationDataSource-Protokoll

Wir wissen zwar nun um die Erstellung eigener Complications, die eine zentrale Frage aber ist noch offen: Wie registriert man Complications im System damit sie auch mit aktuellen Informationen auf einem Ziffernblatt angezeigt werden? Genau an dieser Stelle kommt das neue CLKComplicationDataSource-Protokoll des ClockKit Frameworks ins Spiel. Es ist unsere Aufgabe, eine eigene Controller-Klasse zu erstellen, die konform zum CLKComplicationDataSource-Protokoll ist, und die benötigten Methoden zu implementieren, um darüber unsere Complications bereitzustellen. Diese Controller-Klasse müssen wir dann im Feld Data Source Class der General-Einstellungen unserer WatchKit Extension im Abschnitt Complications Configuration eintragen, damit watchOS weiß, dass es diese Klasse nach der Complication unserer App zu fragen hat. Diesen Bereich in Xcode haben wir bereits zu Beginn des Artikels kennengelernt, da genau in diesem Abschnitt auch von unserer Seite festgelegt wird, welche Templates unsere Complication unterstützt.

So weit die Theorie, werfen wir einmal einen Blick auf die Praxis und die verschiedenen Methoden des CLKComplicationDataSource-Protokolls. Die Methoden des CLKComplicatinDataSource-Protokoll unterteilen sich in vier Bereiche, die unterschiedliche Informationen zu unserer Complication abfragen. Die erste wichtige Methode dabei ist getPlaceholderTemplateForComplication:withHandler:, deren exakte Deklaration im folgenden Listing aufgeführt ist:

Diese Methode dient dazu, eine Vorschau für unsere Complication zu erstellen und zurückzugeben. Diese Vorschau wird dem Nutzer angezeigt, wenn er ein Zifferblatt konfiguriert und durch die verschiedenen zur Verfügung stehenden Complications wechselt. Unterstützt auch unsere Complication das Template und die Größe, in der der Nutzer gerade seine Complication wählt, so taucht auch unsere eigene Complication an dieser Stelle auf, und zwar mit dem CLKComplicationTemplate-Objekt, das wir in der eben genannten Methode zurückgegeben haben. An dieser Stelle werden also keine Live-Daten für die Complication aus unserer App angezeigt, sondern Dummy-Informationen verwendet, die dem Nutzer auch typischerweise schnell und auf einen Blick den Sinn und den Zweck unserer Complication verdeutlichen soll.

Wichtig ist dabei auch das von der Methode übergebene Objekt complication vom Typ CLKComplication. Fast jede Methode des CLKComplicationDataSource -Protokolls liefert ein solches Objekt mit. Die Klasse CLKComplication besteht aus einer Property namens family, die uns Aufschluss darüber gibt, für welches Template eine Complication benötigt wird. Denn wir erinnern uns: Aktuell stehen fünf verschiedene Templates zur Verfügung, für die jeweils eine eigene Complication erstellt werden muss. Mithilfe des übergebenen complication-Objekts können wir prüfen, ob wir überhaupt das aktuelle Template unterstützen und – falls dem so ist – entsprechend eine Complication für eben dieses Template erstellen und zurückgeben.

Äquivalent zu den anfangs vorgestellten Templates kann die Property family einen der folgenden Werte besitzen:

  • ModularSmall (für das Modular Small-Template)
  • ModularLarge (für das Modular Large-Template)
  • UtilitarianSmall (für das Utilitarian Small-Template)
  • UtilitarianLarge (für das Utilitarian Large-Template)
  • CircularSmall (für das Circular Small-Template)

All diese Werte sind in einer eigenen Enumeration der Klasse CLKComplication namens CLKComplicationFamily zusammengefasst, und genau diesem Typ entspricht auch die Property family.

Betrachten wir das ganze nun einmal in der Praxis und ziehen dazu die Complication heran, die wir bereits zuvor für das Template Modular Large erstellt haben. Eine komplette beispielhafte Implementierung der Methode getPlaceholderTemplateForComplication:withHandler: des CLKComplicationDataSource-Protokolls könnte so wie im folgenden Listing gezeigt aussehen:

Zunächst prüfen wir, ob wir das aktuell aktive Template überhaupt unterstützen. Wenn dem so ist, erstellen wir eine entsprechende Complication (in diesem Fall ist es die Vorschau-Complication für die Konfiguration auf dem Ziffernblatt) und übergeben diese zum Abschluss dem handler-Parameter der Methode.

Timeline Entry

So eine Vorschau ist gut und schön, doch natürlich wollen wir unsere Complication auch mit „richtigen“ Daten befüllen und anbieten. Das CLKComplicationDataSource-Protokoll bietet zu diesem Zweck drei verschiedene Methoden, um Complications zu erstellen und an das System zurückzuliefern. Die zunächst einmal wichtigste davon ist getCurrentTimelineEntryForComplication:withHandler:. Auch diese Methode verfügt über einen complication- und einen handler-Parameter, wie wir sie bereits von der zuvor vorgestellten Methode getPlaceholderTemplateForComplication:withHandler: her kennen. Einen großen Unterschied gibt es dann aber doch, wenn man sich einmal die genaue Deklaration dieser Methode im folgenden Listing ansieht:

Das handler-Objekt erwartet nun statt eines CLKComplicationTemplate-Objekts eine Instanz vom Typ CLKComplicationTimelineEntry. Tatsächlich müssen wir in allen Methoden, in denen wir Complications mit Live-Daten aus unserer App erstellen und zurückgeben, ein passendes CLKComplicationTimelineEntry-Objekt erzeugen. Doch was hat es damit auf sich?

So ein CLKComplicationTimelineEntry-Objekt setzt sich aus zwei Informationen zusammen: Der Complication, der angezeigt werden soll (in Form eines CLKComplicationTemplate-Objekts) sowie einer Datums- und Zeitangabe in Form eines NSDate-Objekts. Letzteres ist der spannende Punkt bei diesen Timeline Entries und hängen direkt mit dem neuen Time Travel-Feature von watchOS 2 zusammen. Denn eben dieses Time Travel-Feature erlaubt es, die Zeit auf einem Ziffernblatt mithilfe der Digital Crown vor- und zurückzudrehen. Und bei diesem Vorgang ändert sich natürlich nicht nur die Zeitangabe, sondern auch alle Complications werden entsprechend aktualisiert. Eine Complication muss also bereits vorkonfiguriert für verschiedene Zeitpunkte zur Verfügung stehen, und eben diese Zeitpunkte werden in einem CLKComplicationTimelineEntry-Objekt anhand des NSDate-Parameters definiert. Damit weiß watchOS, ab welchem Zeitpunkt es eine aktuelle oder vorherige Version der Complication auf dem Ziffernblatt anzeigen muss.

Neben dem Time Travel-Feature hat dieses Vorgehen aber auch noch einen ganz anderen Grund. So werden die Complications naturgemäß direkt beim Blick auf das Ziffernblatt dem Nutzer angezeigt. Würde die Apple Watch erst zu diesem Zeitpunkt von allen aktiven Complications aktuelle Daten erfragen, würde das wohl in der Regel zu viel Zeit beanspruchen, als das alle Complications innerhalb der zwei oder drei Sekunden, in denen der Nutzer mal eben auf das Ziffernblatt blickt, geladen wären. Aus diesem Grund hält watchOS mehrere Versionen einer Complication zu verschiedenen Zeitpunkten vor, um so im Laufe des Tages immer die jetzt aktuelle Complication zum passenden Zeitpunkt zu laden und anzuzeigen.

Das Erstellen einer passenden CLKComplicationTimelineEntry-Instanz gestaltet sich dabei sehr einfach: Die Klasse verfügt über einen entsprechenden Initializer, der die Complication sowie den Zeitpunkt, zu dem sie angezeigt werden soll, entgegennimmt. Eine beispielhafte Implementierung der eben vorgestellten Methode getCurrentTimelineEntryForComplication:withHandler: zeigt das folgende Listing:

Die eben vorgestellte Methode getCurrentTimelineEntryForComplication:withHandler: ist dabei die einfachste, denn diese erwartet genau ein CLKComplicationTimelineEntry-Objekt für den aktuellen Zeitpunkt, sprich die Complication, die jetzt angezeigt werden soll. Ergänzend dazu bringt das CLKComplicationDataSource-Protokoll zusätzlich noch die im folgenden Listing definierten zwei Methoden mit:

Diese beiden Methoden erwarten gleich mehrere CLKComplicationTimelineEntry-Objekte in Form eines Arrays, die dem handler-Objekt übergeben werden sollen. Daneben liefern sie in Form des limit -Parameters eine Maximalanzahl für dieses Array mit, die auch keinesfalls überstiegen werden sollte. Der große Unterschied zwischen diesen beiden Methoden liegt daran, dass Erstere nach Complications vor, Letztere nach Complications nach dem im Parameter date übergebenen Zeitpunkt fragt. Sie erstellen und übergeben in diesen Methoden also Complications, die entweder bereits vergangen sind oder erst in Zukunft aktiv werden. Auf diese Art und Weise kann watchOS wie beschrieben bereits mehrere Versionen Ihrer Complication vorhalten und braucht diese nur noch zum passenden Zeitpunkt anzuzeigen (beispielsweise dann, wenn ein Nutzer Time Travel verwendet). Das folgende Listing soll die Arbeit mit diesen Methoden beispielhaft verdeutlichen, indem für die Erstere der beiden Methoden so viele CLKComplicationTimelineEntry-Objekte erzeugt werden die der Anzahl in limit entsprechen.

Zusätzliche Optionen

Die wichtigsten Methoden und Funktionen zur Erstellung und Bereitstellung eigener Complications haben wir nun kennengelernt. Daneben verfügt das CLKComplicationDataSource-Protokoll aber noch über weitere hilfreiche Methoden, deren Implementierung sich von Fall zu Fall lohnen kann. Beispielsweise können Sie mit der im Folgenden deklarierten Methode festlegen, ob und falls ja, in welche „Richtungen“ Sie die Time Travel-Funktion von watchOS 2 in Ihrer Complication unterstützen:

Neben dem bekannten complication-Parameter erhalten Sie wieder ein handler-Objekt, welches einen Wert vom Typ CLKComplicationTimeTravelDirections erwartet. Für diesen Typ stehen Ihnen drei Werte zur Verfügung, die Sie mithilfe des binären Operators | auch miteinander verbinden können:

  • None: Gar keine Unterstützung für Time Travel.
  • Forward: Time Travel wird für zukünftige Ereignisse unterstützt.
  • Backward: Time Travel wird für vergangene Ereignisse unterstützt.

Um so beispielsweise eine komplette Unterstützung für Time Travel festzulegen, können Sie die Methode wie folgt implementieren:

Eine andere spannende Methode des CLKComplicationDataSource-Protokoll ist die Folgende:

Mit dieser Methode können Sie definieren, ob Ihre Complication auch nach Sperrung der Apple Watch weiterhin aktiv Daten darstellt oder stattdessen ausgegraugt werden soll. Der handler-Parameter erwartet dazu einen Wert vom Typ CLKComplicationPrivacyBehavior, der entweder ShowOnLockScreen (Complication ist auch nach Sperrung der Apple Watch aktiv) oder HideOnLockScreen (Complication wird nach Sperrung der Apple Watch ausgebaut) annehmen kann. Eine beispielhafte Implementierung dieser Methode, die die Complication bei gesperrter Apple Watch deaktiviert, sehen Sie im folgenden Listing:

Fazit

Das ClockKit Framework sowie die Möglichkeit, eigene Complications für die Apple Watch zu entwickeln, stellen eine gelungene Ergänzung dar und erlauben es Nutzern, auf schnelle und einfache Weise mit einer App zu interagieren. Freilich sind Complications nicht für jede App geeignet (nicht zuletzt aufgrund ihres extrem geringen Platzes auf dem sowieso schon kleinen Bildschirm), wer in ihnen aber eine Chance sieht, das eigene App-Erlebnis zu verbessern, sollte sich umgehend im Detail mit ClockKit und all seinen Klassen und Funktionen auseinandersetzen.

Previous post

HowToDoIt! Wie schnelllebig doch unsere Zeit ist Folgen 61 bis 70

Next post

SwiftBlog.de Autoren und Mentorenprogramm

The Author

Thomas Sillmann

Thomas Sillmann

Thomas Sillmann ist leidenschaftlicher iOS-App-Entwickler, Trainer und Autor. Freiberuflich tätig programmiert er eigene Apps für den App Store sowie Apps in Form von Kundenaufträgen. Sein Wissen gibt er in Trainings, Workshops und Vorträgen weiter. Mit seiner Begeisterung für das Schreiben hat er bereits zwei erfolgreiche Fachbücher und mehrere Kurzgeschichten veröffentlicht. Thomas lebt und arbeitet in Aschaffenburg.

No Comment

Leave a reply

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.