
L'Apple Watch a rapidement gagné en popularité et est devenue la montre la plus populaire au monde, devant Rolex et d'autres fabricants. L'idée de créer une application pour les montres est présente au bureau 2GIS depuis 2015.
Avant nous, seule Apple elle-même a publié une application à part entière avec une carte sur la montre. L'application Yandex.Map n'affiche que les widgets trafic et le temps de trajet domicile-travail. Yandex.Navigator, Google Maps, Waze et Maps.Me ne sont généralement pas disponibles sur la montre.
En fait, en raison des nombreuses limites du système et de la complexité du développement, les entreprises ne font pas du tout d'applications de surveillance ou les rendent très simples. Vous ne pouvez pas simplement prendre et dessiner une carte sur la montre. Mais nous pourrions.
Jetez un œil sous le chat pour découvrir comment le projet pour animaux de compagnie est devenu un produit complet.
UPD.: Https://github.com/teanet/DemoWatch
Nous avons décidé de faire une carte. C'était quoi au départ?
- Expérience de développement sous surveillance - 2 jours de travail sur un projet de test.
- Expérience avec SpriteKit - 0 jours.
- Expérience d'écriture MapKit - 0 jours.
- Doute que quelque chose puisse mal tourner - ∞.
Itération 1 - Vol de pensée
Nous sommes des gens sérieux, donc pour commencer nous avons décidé d'élaborer un plan de travail. Nous avons pris en compte que nous travaillons dans un sprint bien planifié, nous avons cinq points d'histoire pour les «tâches de petits produits» et une ignorance complète de l'endroit où commencer.
Une carte est une très grande image. Nous pouvons afficher des images sur l'horloge, ce qui signifie que nous pouvons gérer l'affichage de la carte.
Nous avons un service qui peut couper une carte en morceaux:
Si vous coupez une telle image et insérez WKImage, nous obtenons le prototype le plus simple pour cinq cents.
Et si vous ajoutez PanGesture à cette image et installez une nouvelle image à chaque coup, nous obtenons une simulation d'interaction avec la carte.
/ Réjouis-toi / Ça a l'air horrible, ça a l'air à peu près pareil, ça marche encore pire, mais en fait la tâche est terminée.
Itération 2 - prototype minimum
Le téléchargement d'images en continu coûte cher en quelques heures pour une batterie. Oui, et le temps de démarrage lui-même en souffre. Nous voulions obtenir quelque chose de plus complet et réactif. Du coin de l'oreille, nous avons entendu que la montre prend en charge SpriteKit - le seul cadre pour WatchOS, avec la possibilité d'utiliser des coordonnées, de zoomer et de personnaliser toute cette splendeur par vous-même.
Après quelques heures de développement piloté par StackOverflow (SDD), nous obtenons la deuxième itération:
Un SKSpriteNode, un WKPanGestureRecognizer.
/ Réjouissez-vous / Oui, c'est MapKit pour 6 kopecks, pleinement opérationnel. Libération urgente!
Itération 3 - Ajout de vignettes et zoom
Lorsque les émotions étaient endormies, ils se sont demandé où aller ensuite.
Compris que la chose la plus importante:
- Remplacez l'image par des tuiles.
- Attachez 4 tuiles au bundle d'application et connectez-les ensemble.
- Fournissez des images zoomées.
Laissons tomber 4 tuiles dans le bundle d'application, puis mettons-les sur une certaine:
let rootNode = SKSpriteNode()
à l'aide de mathématiques simples, nous les relierons.
Nous zoomons sur WKCrownDelegate:
internal func crownDidRotate( _ crownSequencer: WKCrownSequencer?, rotationalDelta: Double ) { self.scale += CGFloat(rotationalDelta * 2) self.rootNode.setScale(self.scale) }
/ Réjouis-toi / Eh bien, c'est sûr! Quelques corrections et au maître.
Itération 4 - optimiser l'interaction avec la carte
Le lendemain, il s'est avéré que pour SpriteKit anchorPoint n'affecte pas le zoom. Le zoom ignore complètement anchorPoint et se produit par rapport au centre du rootNode. Il s'avère que pour chaque étape de zoom, nous devons ajuster la position.
Il serait également intéressant de charger des tuiles à partir du serveur, plutôt que de stocker le monde entier dans la mémoire de la montre.
N'oubliez pas que les tuiles doivent être liées aux coordonnées afin qu'elles ne se trouvent pas au centre de SKScene, mais aux endroits appropriés sur la carte.
Les tuiles ressemblent à ceci:

Chaque zoomLevel (ci-après «z») a son propre jeu de tuiles. Pour z = 1, nous avons 4 tuiles qui composent le monde entier.

pour z = 2 - pour couvrir le monde entier, vous avez déjà besoin de 16 tuiles,
pour z = 3-64 tuiles.
pour z = 18 ≈ 68 * 10 ^ 9 tuiles.
Maintenant, ils doivent être placés dans le monde de SpriteKit.
La taille d'une tuile est de 256 * 256 pt, ce qui signifie
pour z = 1, la taille du "monde" sera de 512 * 512 pt,
pour z = 2, la taille du "monde" sera égale à 1024 * 1024 pt.
Pour faciliter le calcul, placez les tuiles dans le monde comme suit:

Encodez la tuile:
let kTileLength: CGFloat = 256 struct TilePath { let x: Int let y: Int let z: Int }
Définissez les coordonnées de la tuile dans un tel monde:
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 }
L'emplacement est pratique, car il vous permet de tout mettre dans les coordonnées du monde réel: latitude / longitude = 0, qui est juste au centre du "monde".
La latitude / longitude du monde réel est transformée en notre monde comme suit:
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 ) } }
Avec le zoom nivellement des problèmes ratissés. J'ai dû passer quelques jours de repos pour assembler l'ensemble de l'appareil mathématique et assurer la parfaite fusion des carreaux. Autrement dit, la tuile pour z = 1 devrait idéalement aller dans quatre tuiles pour z = 2 et vice versa, les quatre tuiles pour z = 2 devraient aller dans une tuile pour z = 1.

De plus, il était nécessaire de transformer le zoom linéaire en un zoom exponentiel, car les zooms varient de 1 <= z <= 18, et les échelles de carte comme 2 ^ z.
Un zoom fluide est fourni en ajustant constamment la position des carreaux. Il est important que les tuiles soient cousues exactement au milieu: c'est-à-dire que la tuile de niveau 1 passe en 4 tuiles de niveau 2 avec un zoom de 1,5.
SpriteKit utilise un flotteur sous le capot. Pour z = 18, nous obtenons un étalement des coordonnées (-33 554 432/33 554 432), et la précision de float est de 7 bits. A la sortie, nous avons une erreur de l'ordre de 30 pt. Pour éviter l'apparition de «lacunes» entre les moitiés, nous plaçons la tuile visible aussi près que possible du centre de SKScene.
/ Réjouissez-vous / Après tous ces gestes, nous avons préparé un prototype pour le test.
Date de sortie
Étant donné que l'application n'avait pas vraiment de savoirs traditionnels, nous avons trouvé quelques volontaires pour effectuer un petit test. Ils n'ont trouvé aucun problème particulier et ont décidé de se mettre sur le côté.
Après la sortie, il s'est avéré que sur l'horloge de la première série le processeur n'a pas le temps de dessiner la première trame de la carte en 10 secondes et tombe par timeout. J'ai d'abord dû créer une carte complètement vide pour qu'elle tienne en 10 secondes, puis charger progressivement le substrat. Développez d'abord tous les niveaux de la carte, puis chargez-y des tuiles.
Il a fallu beaucoup de temps pour déboguer le réseau, configurer correctement le cache et une petite empreinte mémoire afin que WatchOS n'essaye pas de tuer notre application aussi longtemps que possible.
Après avoir profilé l'application, nous avons découvert qu'au lieu des tuiles habituelles, vous pouvez utiliser des tuiles Retina, presque sans nuire au temps de chargement et à la consommation de mémoire, et dans la nouvelle version, elles y étaient déjà passées.
Résultats et plans pour l'avenir
Nous pouvons déjà afficher sur la carte un itinéraire avec des manœuvres construites dans l'application principale. La fonctionnalité sera disponible dans l'une des prochaines versions.
Le projet, qui semblait initialement impossible, s'est avéré extrêmement utile pour moi personnellement. J'utilise souvent l'application pour comprendre s'il est temps de descendre au bon arrêt. Je pense qu'en hiver ce sera encore plus utile.
Dans le même temps, il était une fois de plus convaincu que la complexité du projet, la confiance des autres dans la réussite de la tâche ou la disponibilité de temps libre au travail n'étaient pas si importantes. L'essentiel est le désir de faire un projet et un mouvement ennuyeux et progressif vers le but. En conséquence, nous avons un MapKit à part entière, qui est presque illimité et fonctionne avec 3 WatchOS. Il peut être modifié à votre guise sans attendre qu'Apple déploie l'API appropriée pour le développement.
PS Pour ceux qui sont intéressés, je peux présenter un projet fini. Le niveau de code y est loin d'être produit. Mais, selon le principe militaire, peu importe comment cela fonctionne, l'essentiel est que cela fonctionne!