2GIS está a tu alcance. Cómo agregamos un mapa al Apple Watch


Apple Watch rápidamente ganó popularidad y se convirtió en el reloj más popular del mundo, por delante de Rolex y otros fabricantes. La idea de crear una aplicación para relojes ha estado en la oficina de 2GIS desde 2015.


Antes que nosotros, solo Apple lanzó una aplicación completa con una tarjeta en el reloj. La aplicación Yandex.Map solo muestra widgets de tráfico y tiempo de viaje a casa y al trabajo. Yandex.Navigator, Google Maps, Waze y Maps.Me generalmente no están disponibles en el reloj.


De hecho, debido a las muchas limitaciones del sistema y la complejidad del desarrollo, las compañías no hacen aplicaciones de vigilancia o las hacen muy simples. No puedes simplemente tomar y dibujar un mapa en el reloj. Pero pudimos.


Eche un vistazo debajo del gato para descubrir cómo el proyecto de la mascota se ha convertido en un producto completo.


UPD.: Https://github.com/teanet/DemoWatch


Decidimos hacer un mapa. ¿Qué fue al principio?


  1. Experiencia de desarrollo en el reloj: 2 días de trabajo en un proyecto de prueba.
  2. Experiencia con SpriteKit - 0 días.
  3. Experiencia de escritura de MapKit: 0 días.
  4. Dudas de que algo salga mal - ∞.

Iteración 1 - Vuelo del pensamiento


Somos personas serias, así que para empezar decidimos elaborar un plan de trabajo. Tomamos en cuenta que trabajamos en un sprint bien planificado, tenemos cinco puntos de historia para "tareas de productos pequeños" y completa ignorancia de por dónde comenzar.


Un mapa es una imagen muy grande. Podemos mostrar imágenes en el reloj, lo que significa que podemos manejar la visualización de la tarjeta.


Tenemos un servicio que puede cortar una tarjeta en pedazos:



Si corta una imagen así y la coloca en WKImage, obtenemos el prototipo de trabajo más simple por cinco centavos.


Y si agrega PanGesture a esta imagen e instala una nueva imagen en cada deslizamiento, obtenemos una simulación de interacción con la tarjeta.


/ Regocijarse / Suena horrible, se ve casi igual, funciona aún peor, pero de hecho la tarea se ha completado.


Iteración 2 - prototipo mínimo


Las descargas continuas de imágenes son caras para una batería en horas. Sí, y el tiempo de arranque en sí sufre. Queríamos obtener algo más completo y receptivo. Por el rabillo del oído, escuchamos que el reloj tiene soporte para SpriteKit, el único marco de WatchOS con la capacidad de usar coordenadas, hacer zoom y personalizar todo este esplendor por ti mismo.


Después de un par de horas de StackOverflow Driven Development (SDD) obtenemos la segunda iteración:
Un SKSpriteNode, un WKPanGestureRecognizer.



/ Alégrate / Sí, esto es MapKit para 6 kopecks, funcionando completamente. Lanzamiento urgente!


Iteración 3: adición de mosaicos y zoom


Cuando las emociones estaban dormidas, se preguntaban a dónde ir después.


Entendido que lo más importante:


  • Reemplace la imagen con mosaicos.
  • Adjunte 4 mosaicos al paquete de aplicaciones y conéctelos.
  • Proporcionar imágenes con zoom.
    Dejemos caer 4 mosaicos en el paquete de la aplicación, luego póngalos en uno determinado:

let rootNode = SKSpriteNode() 

Con la ayuda de las matemáticas simples, los conectaremos entre sí.
Hacemos zoom a través de WKCrownDelegate:


 internal func crownDidRotate( _ crownSequencer: WKCrownSequencer?, rotationalDelta: Double ) { self.scale += CGFloat(rotationalDelta * 2) self.rootNode.setScale(self.scale) } 


/ Regocíjate / Bueno, ¡eso es seguro! Un par de correcciones, y para el maestro.


Iteración 4: optimización de la interacción con el mapa


Al día siguiente, resultó que para SpriteKit anchorPoint no afecta el zoom. El zoom ignora por completo el punto de anclaje y se produce en relación con el centro del nodo raíz. Resulta que para cada paso de zoom necesitamos ajustar la posición.


También sería bueno cargar mosaicos desde el servidor, en lugar de almacenar todo el mundo en la memoria del reloj.
No olvide que las fichas deben estar vinculadas a las coordenadas para que no se encuentren en el centro de SKScene, sino en los lugares apropiados del mapa.


Los azulejos se parecen a esto:



Cada zoomLevel (en adelante "z") tiene su propio conjunto de mosaicos. Para z = 1, tenemos 4 mosaicos que forman el mundo entero.



para z = 2: para cubrir todo el mundo, ya necesita 16 fichas,
para z = 3 - 64 fichas.
para z = 18 ≈ 68 * 10 ^ 9 fichas.
Ahora necesitan ser puestos en el mundo de SpriteKit.


El tamaño de un mosaico es de 256 * 256 pt, lo que significa
para z = 1, el tamaño del "mundo" será 512 * 512 pt,
para z = 2 el tamaño del "mundo" será igual a 1024 * 1024 pt.
Para facilitar el cálculo, coloque fichas en el mundo de la siguiente manera:



Codificar el mosaico:


 let kTileLength: CGFloat = 256 struct TilePath { let x: Int let y: Int let z: Int } 

Defina la coordenada del mosaico en un mundo así:


 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 } 

La ubicación es conveniente, ya que le permite llevar todo a las coordenadas del mundo real: latitud / longitud = 0, que está justo en el centro del "mundo".


La latitud / longitud del mundo real se transforma en nuestro mundo de la siguiente manera:


 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 ) } } 

Con el zoom de nivelación problemas rastrillados. Tuve que pasar un par de días libres para armar todo el aparato matemático y asegurar la fusión perfecta de los mosaicos. Es decir, el mosaico para z = 1 idealmente debería ir en cuatro mosaicos para z = 2 y viceversa, los cuatro mosaicos para z = 2 deberían ir en un mosaico para z = 1.



Además, era necesario convertir el zoom lineal en uno exponencial, ya que los zooms varían de 1 <= z <= 18, y el mapa se escala como 2 ^ z.


El zoom suave se proporciona ajustando constantemente la posición de los mosaicos. Es importante que las fichas estén cosidas exactamente en el medio: es decir, que la ficha de nivel 1 vaya a 4 fichas de nivel 2 con un zoom de 1.5.


SpriteKit usa un flotador debajo del capó. Para z = 18, obtenemos una extensión de coordenadas (-33 554 432/33 554 432), y la precisión de flotación es de 7 bits. A la salida, tenemos un error en la región de 30 pt. Para evitar la aparición de "espacios" entre mitades, colocamos el mosaico visible lo más cerca posible del centro de SKScene.


/ Alégrate / Después de todos estos gestos, tenemos un prototipo listo para probar.


Fecha de lanzamiento


Como la aplicación realmente no tenía TK, encontramos un par de voluntarios para realizar algunas pruebas. No encontraron problemas particulares y decidieron lanzarse a un lado.


Después del lanzamiento, resultó que en el reloj de la primera serie el procesador no tiene tiempo para dibujar el primer fotograma de la tarjeta en 10 segundos y cae por tiempo de espera. Inicialmente tuve que crear una tarjeta completamente vacía para que quepa en 10 segundos, y luego cargar gradualmente el sustrato. Primero desarrolle todos los niveles del mapa, y luego cargue los mosaicos para ellos.


Tomó mucho tiempo depurar la red, configurar correctamente la caché y una pequeña huella de memoria para que WatchOS no intentara matar nuestra aplicación durante el mayor tiempo posible.


Una vez perfilada la aplicación, descubrimos que en lugar de los mosaicos habituales, puede usar mosaicos Retina, casi sin dañar el tiempo de carga y el consumo de memoria, y en la nueva versión ya cambiaron a ellos.


Resultados y planes para el futuro


Ya podemos mostrar en el mapa una ruta con maniobras integradas en la aplicación principal. La función estará disponible en uno de los próximos lanzamientos.


El proyecto, que inicialmente parecía imposible, resultó ser extremadamente útil para mí personalmente. A menudo uso la aplicación para comprender si es hora de bajar en la parada correcta. Creo que en invierno será aún más útil.


Al mismo tiempo, volvió a estar convencido de que la complejidad del proyecto, la fe de los demás en el éxito de la tarea o la disponibilidad de tiempo libre en el trabajo no eran tan importantes. Lo principal es el deseo de hacer un proyecto y un movimiento aburrido y gradual hacia la meta. Como resultado, tenemos un MapKit completo, que es casi ilimitado y funciona con 3 WatchOS. Se puede modificar como desee sin esperar a que Apple implemente la API adecuada para el desarrollo.


PD: Para aquellos interesados, puedo diseñar un proyecto terminado. El nivel del código está lejos de la producción. Pero, de acuerdo con el principio militar, no importa cómo funcione, ¡lo principal es que funciona!

Source: https://habr.com/ru/post/es422935/


All Articles