
Die Apple Watch gewann schnell an Popularität und wurde vor Rolex und anderen Herstellern zur beliebtesten Uhr der Welt. Die Idee, eine Anwendung für Uhren zu erstellen, ist seit 2015 im 2GIS-Büro.
Vor uns hat nur Apple selbst eine vollwertige Anwendung mit einer Karte auf der Uhr veröffentlicht. Die Yandex.Map-Anwendung zeigt nur Verkehrs-Widgets und die Reisezeit nach Hause und zur Arbeit an. Yandex.Navigator, Google Maps, Waze und Maps.Me sind auf der Uhr im Allgemeinen nicht verfügbar.
Aufgrund der vielen Einschränkungen des Systems und der Komplexität der Entwicklung stellen Unternehmen entweder überhaupt keine Uhrenanwendungen her oder machen sie sehr einfach. Sie können nicht einfach eine Karte auf die Uhr nehmen und zeichnen. Aber wir könnten.
Werfen Sie einen Blick unter die Katze, um herauszufinden, wie aus dem Haustierprojekt ein vollständiges Produkt geworden ist.
UPD .: https://github.com/teanet/DemoWatch
Wir beschlossen, eine Karte zu machen. Was war am Anfang?
- Entwicklungserfahrung auf der Uhr - 2 Tage Arbeit an einem Testprojekt.
- Erfahrung mit SpriteKit - 0 Tage.
- MapKit-Schreiberfahrung - 0 Tage.
- Zweifel, dass etwas schief gehen könnte - ∞.
Iteration 1 - Flug des Denkens
Wir sind ernsthafte Menschen, deshalb haben wir uns zunächst entschlossen, einen Arbeitsplan zu erstellen. Wir haben berücksichtigt, dass wir in einem gut geplanten Sprint arbeiten. Wir haben fünf Story-Punkte für „Aufgaben mit kleinen Produkten“ und wissen nicht, wo wir anfangen sollen.
Eine Karte ist ein sehr großes Bild. Wir können Bilder auf der Uhr anzeigen, was bedeutet, dass wir die Anzeige der Karte übernehmen können.
Wir haben einen Service, der eine Karte in Stücke schneiden kann:
Wenn Sie ein solches Bild ausschneiden und WKImage einfügen, erhalten wir den einfachsten Prototyp für fünf Cent.
Wenn Sie diesem Bild PanGesture hinzufügen und bei jedem Wischen ein neues Bild installieren, wird die Interaktion mit der Karte simuliert.
/ Freut euch / Es klingt schrecklich, es sieht ungefähr gleich aus, es funktioniert noch schlimmer, aber tatsächlich ist die Aufgabe erledigt.
Iteration 2 - minimaler Prototyp
Kontinuierliche Bilddownloads sind für einen Akku in Stunden teuer. Ja, und die Startzeit selbst leidet. Wir wollten etwas vollständigeres und reaktionsfähigeres bekommen. Aus den Augenwinkeln hörten wir, dass die Uhr SpriteKit unterstützt - das einzige Framework für WatchOS, mit der Möglichkeit, Koordinaten zu verwenden, zu zoomen und all diese Pracht für sich selbst anzupassen.
Nach ein paar Stunden StackOverflow Driven Development (SDD) erhalten wir die zweite Iteration:
Ein SKSpriteNode, ein WKPanGestureRecognizer.
/ Freut euch / Ja, das ist MapKit für 6 Kopeken, voll funktionsfähig. Dringende Freigabe!
Iteration 3 - Hinzufügen von Kacheln und Zoom
Wenn die Gefühle schliefen, fragten sie sich, wohin sie als nächstes gehen sollten.
Verstanden, dass das Wichtigste:
- Ersetzen Sie das Bild durch Kacheln.
- Befestigen Sie 4 Kacheln am Anwendungspaket und verbinden Sie sie miteinander.
- Stellen Sie Zoombilder bereit.
Lassen Sie uns 4 Kacheln in das Anwendungspaket legen und sie dann auf eine bestimmte legen:
let rootNode = SKSpriteNode()
Mit Hilfe der einfachen Mathematik werden wir sie miteinander verbinden.
Wir zoomen durch WKCrownDelegate:
internal func crownDidRotate( _ crownSequencer: WKCrownSequencer?, rotationalDelta: Double ) { self.scale += CGFloat(rotationalDelta * 2) self.rootNode.setScale(self.scale) }
/ Freut euch / Nun, das ist sicher! Ein paar Korrekturen und an den Meister.
Iteration 4 - Optimierung der Interaktion mit der Karte
Am nächsten Tag stellte sich heraus, dass AnchPoint für SpriteKit den Zoom nicht beeinflusst. Zoom ignoriert anchorPoint vollständig und tritt relativ zur Mitte des rootNode auf. Es stellt sich heraus, dass wir für jeden Zoomschritt die Position anpassen müssen.
Es wäre auch schön, Kacheln vom Server zu laden, anstatt die ganze Welt im Speicher der Uhr zu speichern.
Vergessen Sie nicht, dass die Kacheln an die Koordinaten gebunden werden sollten, damit sie nicht im Zentrum von SKScene liegen, sondern an den entsprechenden Stellen auf der Karte.
Fliesen sehen ungefähr so aus:

Jede zoomLevel (im Folgenden „z“) verfügt über einen eigenen Satz von Kacheln. Für z = 1 haben wir 4 Kacheln, aus denen die ganze Welt besteht.

für z = 2 - um die ganze Welt abzudecken, brauchst du bereits 16 Kacheln,
für z = 3 - 64 Fliesen.
für z = 18 ≈ 68 * 10 ^ 9 Kacheln.
Jetzt müssen sie in die Welt von SpriteKit aufgenommen werden.
Die Größe einer Kachel beträgt 256 * 256 pt, was bedeutet
für z = 1 beträgt die Größe der "Welt" 512 * 512 pt,
für z = 2 ist die Größe der "Welt" gleich 1024 * 1024 pt.
Legen Sie zur Vereinfachung der Berechnung die Kacheln wie folgt in die Welt:

Codieren Sie die Kachel:
let kTileLength: CGFloat = 256 struct TilePath { let x: Int let y: Int let z: Int }
Definieren Sie die Koordinate der Kachel in einer solchen Welt:
var position: CGPoint { let x = CGFloat(self.x) let y = CGFloat(self.y) let offset: CGFloat = pow(2, CGFloat(self.z - 1)) return CGPoint(x: kTileLength * ( -offset + x ), y: kTileLength * ( offset - y - 1 )) } var center: CGPoint { return self.position + CGPoint(x: kTileLength, y: kTileLength) * 0.5 }
Die Lage ist günstig, da Sie damit alles in die Koordinaten der realen Welt bringen können: Breite / Länge = 0, die sich genau im Zentrum der "Welt" befindet.
Der Breiten- / Längengrad der realen Welt wird wie folgt in unsere Welt umgewandelt:
extension CLLocationCoordinate2D { // ( -1 < TileLocation < 1 ) func tileLocation() -> CGPoint { var siny = sin(self.latitude * .pi / 180) siny = min(max(siny, -1), 1) let y = CGFloat(log( ( 1 + siny ) / ( 1 - siny ))) return CGPoint( x: kTileLength * ( 0.5 + CGFloat(self.longitude) / 360 ), y: kTileLength * ( 0.5 - y / ( 4 * .pi ) ) ) } // zoomLevel func location(for z: Int) -> CGPoint { let tile = self.tileLocation() let zoom: CGFloat = pow(2, CGFloat(z)) let offset = kTileLength * 0.5 return CGPoint( x: (tile.x - offset ) * zoom, y: (-tile.y + offset) * zoom ) } }
Mit Zoom-Leveling geharkt Probleme. Ich musste ein paar Tage frei verbringen, um den gesamten mathematischen Apparat zusammenzustellen und die perfekte Verschmelzung der Kacheln sicherzustellen. Das heißt, die Kachel für z = 1 sollte idealerweise in vier Kacheln für z = 2 und umgekehrt, die vier Kacheln für z = 2 sollten in eine Kachel für z = 1 gehen.

Außerdem musste der lineare Zoom in einen exponentiellen umgewandelt werden, da die Zooms von 1 <= z <= 18 variieren und die Karte wie 2 ^ z skaliert.
Ein sanfter Zoom wird durch ständiges Anpassen der Position der Kacheln erreicht. Es ist wichtig, dass die Kacheln genau in der Mitte gestickt sind: Das heißt, dass die Kachel der Ebene 1 in 4 Kacheln der Ebene 2 mit einem Zoom von 1,5 eingeteilt wird.
SpriteKit verwendet einen Schwimmer unter der Haube. Für z = 18 erhalten wir eine Koordinatenverteilung (-33 554 432/33 554 432), und die Genauigkeit von float beträgt 7 Bit. Am Ausgang haben wir einen Fehler im Bereich von 30 pt. Um das Auftreten von "Lücken" zwischen den Hälften zu vermeiden, platzieren wir die sichtbare Kachel so nahe wie möglich an der Mitte von SKScene.
/ Freut euch / Nach all diesen Gesten haben wir einen Prototyp zum Testen vorbereitet.
Erscheinungsdatum
Da die Anwendung nicht wirklich TK hatte, fanden wir ein paar Freiwillige, die ein wenig testen konnten. Sie fanden keine besonderen Probleme und beschlossen, zur Seite zu rollen.
Nach der Veröffentlichung stellte sich heraus, dass der Prozessor auf der Uhr der ersten Serie keine Zeit hat, den ersten Frame der Karte in 10 Sekunden zu zeichnen, und fällt durch Timeout ab. Ich musste zunächst eine Karte erstellen, die in 10 Sekunden vollständig leer war, und dann das Substrat nach und nach laden. Entwickeln Sie zuerst alle Ebenen der Karte - und laden Sie dann Kacheln für sie.
Das Debuggen des Netzwerks, das ordnungsgemäße Konfigurieren des Caches und ein kleiner Speicherbedarf haben viel Zeit in Anspruch genommen, damit WatchOS nicht versucht, unsere Anwendung so lange wie möglich zu beenden.
Nachdem wir die Anwendung profiliert hatten, stellten wir fest, dass Sie anstelle der üblichen Kacheln Retina-Kacheln verwenden können, ohne die Ladezeit und den Speicherverbrauch zu beeinträchtigen. In der neuen Version wurden sie bereits auf diese Kacheln umgestellt.
Ergebnisse und Pläne für die Zukunft
Auf der Karte können wir bereits eine Route mit Manövern anzeigen, die in der Hauptanwendung integriert sind. Die Funktion wird in einer der kommenden Versionen verfügbar sein.
Das Projekt, das zunächst unmöglich schien, erwies sich für mich persönlich als äußerst nützlich. Ich benutze die Anwendung oft, um zu verstehen, ob es Zeit ist, an der richtigen Haltestelle auszusteigen. Ich glaube, dass es im Winter noch nützlicher sein wird.
Gleichzeitig war er erneut davon überzeugt, dass die Komplexität des Projekts, das Vertrauen anderer in den Erfolg der Aufgabe oder die Verfügbarkeit von Freizeit bei der Arbeit nicht so wichtig sind. Die Hauptsache ist der Wunsch, ein Projekt zu machen und eine langweilige, schrittweise Bewegung in Richtung des Ziels. Als Ergebnis haben wir ein vollwertiges MapKit, das nahezu unbegrenzt ist und mit 3 WatchOS funktioniert. Es kann nach Ihren Wünschen geändert werden, ohne darauf zu warten, dass Apple die entsprechende API für die Entwicklung bereitstellt.
PS Für Interessierte kann ich ein fertiges Projekt auslegen. Die Code-Ebene dort ist weit von der Produktion entfernt. Aber nach dem militärischen Prinzip spielt es keine Rolle, wie es funktioniert, die Hauptsache ist, dass es funktioniert!