Cómo lanzamos 2GIS en CarPlay y todavía desenredamos


Hola Mi nombre es Vanya, estoy escribiendo una aplicación móvil 2GIS para iOS. Hoy habrá una historia sobre cómo apareció nuestro navegador en CarPlay. Le diré cómo con dicha documentación y herramientas inacabadas creamos un producto que funciona y lo colocamos en la AppStore.


Algunas palabras sobre CarPlay



Primero, un pequeño material para comprender algunos aspectos de CarPlay y las razones por las que tomamos ciertas decisiones.


CarPlay no es un sistema operativo dentro de otro sistema operativo, ya que muchos artículos escriben sobre él. En términos generales, CarPlay es un protocolo para trabajar con una pantalla externa de la pantalla de la unidad principal; sonido de los altavoces del automóvil; pantallas táctiles, paneles táctiles, arandelas y otros dispositivos de entrada.


Es decir, todo el código ejecutable se encuentra directamente en la aplicación principal (¡ni siquiera en una extensión separada!) Esto es muy bueno: para obtener nuevas funciones, no necesita actualizar la radio o incluso la máquina, solo necesita actualizar iOS.


En WWDC 2018 Keynote, se nos presentó la oportunidad de crear aplicaciones de navegación para CarPlay, lo que nos hizo muy felices. Inmediatamente después de la presentación, enviamos una solicitud de permiso para desarrollar para CarPlay. En la solicitud, fue necesario demostrar que nuestra aplicación es capaz de navegar.


Mientras esperábamos una respuesta de Apple, hubo una conferencia en la que, utilizando la aplicación de ejemplo CountryRoads, hablamos sobre trabajar con CarPlay.framework. La conferencia no habló sobre las trampas y sutilezas al trabajar con CarPlay, pero mencionó que después de conectarse a la radio CarPlay, la aplicación funcionará en modo de fondo.


Primer palo en las ruedas


La aplicación en el fondo nos decepcionó. Había dos razones para esto:


  1. No trabajamos en segundo plano. Una vez abandonada esta limitación por razones técnicas y para la conservación de energía.
  2. Nuestro mapa está escrito en OpenGL (sí, en desuso, sí, no en Metal, todos lo sabemos), y OpenGL en segundo plano no funciona. En el mejor de los casos, se obtiene una vista en negro y, en el peor, se bloquea.

Todavía era posible hacer frente al trabajo en segundo plano, pero la tarjeta definitivamente necesitaba ser resuelta. Entonces surgió la idea de pasar por el estándar MKMapView. Hasta que comenzaste a arrojarnos piedras por la idea de usar tarjetas Apple estándar, explicaré: íbamos a usar MKMapView, pero no las tarjetas Apple.


El hecho es que MKMapView puede cargar mosaicos de terceros. Las baldosas son contenedores rectangulares especiales para texturas. Acabamos de ser un servochka que sabe dar azulejos. Hay código de implementación en GitHub.


Respuesta de Apple


Recibimos una respuesta de Apple, en la que, además del permiso para desarrollar, también recibimos documentación "para la élite", el código de la aplicación de ejemplo CountryRoads (se mostró en la conferencia WWDC) y, lo más importante, la clave de capacidad privada com.apple.developer.carplay-maps . Esta clave está escrita en el archivo de derechos con el valor YES, para que el sistema comprenda que puede procesar eventos desde CarPlay cuando se inicia su aplicación.


Sin esperar el sprint con las historias seleccionadas para el desarrollo, subí para descargar Xcode Beta. El primer intento de recolectar 2GIS fue un fracaso. Pero el proyecto de aplicación de muestra CoutryRoads pudo ensamblarse para el simulador.


Antes de cada apertura de la ventana del simulador CarPlay, esta última tenía que personalizarse a través de dicha ventana:



Para hacer esto, tenía que escribir una línea en la terminal: por defaults write com.apple.iphonesimulator CarPlayExtraOptions -bool YES


Por alguna razón, esto no funcionó: tuve que ejecutarlo en casi el simulador más pequeño con una resolución de 800 × 480 puntos y una escala × 2. Por el momento, esta configuración funciona y ayuda mucho.


Habiendo creado mi proyecto de muestra y armado con documentación, comencé a entender lo que estaba sucediendo.
Lo primero que me di cuenta: las aplicaciones de navegación para CarPlay consisten en vista base y capas de plantillas.



La vista base es tu mapa. En esta capa solo debe haber un mapa, no otras vistas y controles.


Las plantillas son un conjunto obligatorio casi no personalizable de elementos de la interfaz de usuario para mostrar rutas, maniobras, todo tipo de listas, etc.


Desarrollo beta


Pasemos a escribir código. Lo primero que debe hacer es implementar un par de métodos CPApplicationDelegate necesarios en el archivo ApplicationDelegate.


 func application( _ application: UIApplication, didConnectCarInterfaceController controller: CPInterfaceController, to window: CPWindow ) {} func application( _ application: UIApplication, didDisconnectCarInterfaceController controller: CPInterfaceController, from window: CPWindow ) {} 

Veamos la firma:


Con UIApplication, todo está claro.
CPWindow es el sucesor de UIWindow, una ventana para la pantalla externa de la unidad principal de la radio.
CPInterfaceController: algo así como un análogo de UINavigationController, solo de CarPlay.framework.


Ahora procedemos directamente a la implementación del método.


 func application( _ application: UIApplication, didConnectCarInterfaceController controller: CPInterfaceController, to window: CPWindow ) { let carMapViewController = CarMapViewController( interfaceController: controller ) let navigationController = UINavigationController( rootViewController: carMapViewController ) window.rootViewController = navigationController } 

En didConnect, debe escribir un código similar al que solíamos ver en didFinishLaunching. CarMapViewController es una vista base (el controlador es en realidad, pero está bien), según la documentación.


Aquí está la foto que finalmente obtuve:



En algún momento en este momento, me di cuenta de que el nuevo sistema de compilación Xcode está habilitado de manera predeterminada y, muy probablemente, debido a esto, 2GIS no lo hará.


Abrí Xcode, instalé el sistema de construcción heredado (o más bien estable, llamémoslo por su nombre), y mi teoría fue confirmada: se ensambló 2GIS.


Después de configurar la misma clave de capacidad, inicié 2GIS en CarPlay y no vi ningún registro sobre el cambio de la aplicación al modo de fondo. Se volvió aún más incomprensible, porque los ingenieros de Apple de la escena dijeron sobre el modo de fondo, pero, por otro lado, nos prometieron un contentView de UIAlertView, y como resultado, UIAlertView quedó en desuso.


Habiendo decidido que debería ser así, no me molesté con MKMapView. Nos privaría de la conexión y nos haría reescribir la representación de las rutas.


Problema de tarjeta individual


No tuve tiempo de alegrarme por la noticia de que CarPlay tendrá nuestro mapa, ya que el siguiente problema me enfrentó: debido a las características técnicas, solo puede haber un mapa.
Una solución rápida a este problema fue, aunque no muy elegante.


Por lo general, cuando usa 2GIS en CarPlay, el teléfono está bloqueado y se encuentra en algún lugar del estante. Por lo tanto, el mapa en este momento en el teléfono no es realmente necesario (no hará daño buscar, por supuesto). Por lo tanto, cuando conectamos el teléfono a CarPlay, decidimos tomar la tarjeta de la aplicación principal y mostrarla en la pantalla CarPlay de la radio. Y cuando esté desconectado, respectivamente, regrese a la aplicación en el teléfono.


Sí, es una solución en sí misma, pero es rápida, todavía funciona y no tuvo que patear un par de otros comandos para remachar MVP.


Controles en el mapa


Entonces, tenemos nuestro mapa en la pantalla de la radio. Ahora era necesario hacer lo primero y obvio para cualquier mapa: controles de zoom, ubicación actual y movimiento del mapa.



Comencemos con el zoom y la ubicación actual, porque estos controles están ubicados en el mapa y no son UIControl ordinarios. Como escribí anteriormente, solo el mapa está en la vista base.


Para colocar estos controles en la tarjeta, tuve que volver a la documentación y la aplicación de muestra nuevamente. Allí leí sobre la primera plantilla: CPMapTemplate.



CPMapTemplate: una plantilla transparente para mostrar algunos controles en el mapa y análogos de la barra de navegación. Se crea y configura de esta manera:


 let mapTemplate = CPMapTemplate() self.interfaceController.setRootTemplate(mapTemplate, animated: false) 

A continuación, debe crear estos controles y colocarlos en la tarjeta.


 let zoomInButton = CPMapButton(…) let zoomOutButton = CPMapButton(…) let myLocationButton = CPMapButton(…) self.mapTemplate.mapButtons = [ zoomInButton, zoomOutButton, myLocationButton ] 

Pero la matriz mapButtons resultó ser divertida, porque no importa cuántos elementos coloque en ella, solo tomará los primeros tres elementos y los mostrará en la pantalla. No recibirá ningún error en el registro o aserciones.


Luego pude ver cómo puedo hacer que el mapa se mueva, y encontré esto en la documentación:


 Navigation apps are designed to work with a variety of car input devices, and CarPlay does not support direct user interaction in the base view (apps do not directly receive tap or drag events). 

Extraño, pensé, y pude ver cómo se hace esto en la aplicación de ejemplo CountryRoads. La respuesta es a través de esta interfaz:



No es muy conveniente, pero de otra manera, la documentación no mentirá, ¿verdad?


Como el lugar para los controles en el mapa se había agotado, era necesario hacer un botón para poner el mapa en el modo "arrastrar" en este análogo de la barra de navegación.


 let panButton = CPBarButton(…) self.mapTemplate.leadingNavigationBarButtons = [panButton] self.mapTemplate.trailingNavigationBarButtons = [] 

Pero las matrices de LeadingNavigationBarButtons y trailingNavigationBarButtons, también, no estuvieron exentas de una broma: cuántos elementos empujan, tomarán solo los dos primeros. También sin errores en el registro y aserciones.


Y para activar y desactivar el modo de arrastrar y soltar de la tarjeta, debe escribir:


 self.mapTemplate.showPanningInterface(animated: true) self.mapTemplate.dismissPanningInterface(animated: true) 

Crear y mostrar rutas en un mapa


Luego, comencé a reutilizar nuestra API existente para construir rutas.


Solo para una demostración y entender qué y cómo hacer, decidí tomar dos puntos y construir una ruta entre ellos. El punto A era la ubicación del usuario, y el punto B era nuestra oficina principal en Novosibirsk.


Código
 let choice0 = CPRouteChoice( summaryVariants: ["46 "], additionalInformationVariants: ["  "], selectionSummaryVariants: ["1  7 "] ) let choice1 = CPRouteChoice( summaryVariants: ["46 "], additionalInformationVariants: ["  "], selectionSummaryVariants: [“1  11 "] ) let startItem = MKMapItem(…) let endItem = MKMapItem(…) endItem.name = ",  ” let trip = CPTrip( origin: startItem, destination: endItem, routeChoices: [choice0, choice1] ) let tripPreviewTextConfiguration = CPTripPreviewTextConfiguration( startButtonTitle: " ”, additionalRoutesButtonTitle: “”, overviewButtonTitle: "" ) self.mapTemplate.showTripPreviews( [trip], textConfiguration: tripPreviewTextConfiguration ) 

En la pantalla tenemos un control con una descripción de la ruta:



Modo de navegación


Las rutas son buenas, pero la característica principal del navegador es la navegación. Para que aparezca, debe escribir lo siguiente:


 func mapTemplate( _ mapTemplate: CPMapTemplate, startedTrip trip: CPTrip, using routeChoice: CPRouteChoice ) { self.navigationSession = self.mapTemplate.startNavigationSession(for: trip) } 

CPNavigationSession: una clase con la que puede mostrar algunos elementos de la interfaz de usuario que solo son necesarios en el modo de navegación.


Para mostrar la maniobra, debe:


 let maneuver = CPManeuver() maneuver.symbolSet = CPImageSet( lightContentImage: icon, darkContentImage: darkIcon ) maneuver.instructionVariants = [". "] maneuver.initialTravelEstimates = CPTravelEstimates(…) self.navigationSession?.upcomingManeuvers = [maneuver] 

Luego en la pantalla de la radio obtenemos esto:



Para actualizar las imágenes a maniobrar, debe:


 let estimates = CPTravelEstimates(…) self.navigationSession?.updateEstimates(estimates, for: maneuver) 

¡Simplemente funciona!


Cuando la funcionalidad básica para el navegador estaba lista, decidí mostrar este arte en una presentación interna. La presentación fue un éxito: todos tuvieron la idea de completar, probar y lanzar el navegador lo antes posible.


En primer lugar, pedimos una unidad principal real con soporte CarPlay. Y luego, como dicen, comenzó el calor.


PIONEER AVH-Z500BT


Perfiles de provisión


Debido a la adición de una nueva clave de capacidad, los perfiles deben regenerarse. En el desarrollo normal, no pensamos en ello, porque Xcode hará todo por sí mismo. Pero no en el caso de una clave privada.


 Code Signing Error: Automatic signing is unable to resolve an issue with the "v4ios" target's entitlements. Automatic signing can't add the com.apple.developer.carplay-maps entitlement to your provisioning profile. Switch to manual signing and resolve the issue by downloading a matching provisioning profile from the developer website. 

También rompió nuestro CI, porque para la distribución local de versiones de aplicaciones usamos una cuenta empresarial, en la que no solicitamos permiso para desarrollar la aplicación para CarPlay. Pero esta es una historia completamente diferente.


Depuración


Puede conectarse a CarPlay a través de Bluetooth o Lightning. La práctica muestra que el segundo método es mucho más popular. Nuestra radio en Bluetooth no sabía cómo, así que durante el desarrollo tuve que usar la depuración de Wi-Fi. Si lo probaste en proyectos más difíciles que hello world, entonces sabes qué demonios es.


Y para aquellos que no lo han intentado, les digo:

Recogí la aplicación por cable al teléfono, y solo entonces, conectando el teléfono a CarPlay, a través de Wi-Fi, lo cargué al teléfono y lo ejecuté durante varios minutos.
Copiar la aplicación al teléfono fue de aproximadamente 3 minutos, iniciar la aplicación durante aproximadamente un minuto, y solo después de comenzar la parada en los puntos de interrupción fue solo 15 segundos después.


Y luego me resultó muy interesante por qué Apple no hizo ningún DevKit (por lo que, a la manera de Apple, simplemente funciona y eso es todo). No era muy conveniente armar un banco de pruebas sin él. Hasta ahora, una vez cada dos semanas, algo se cae; hay que recordar de las fotos en qué quedarse. Es bueno que el administrador, al armar este stand, dijera qué y por qué.


El mejor marco que hemos hecho


Al final, cuando todo se ensambló en un dispositivo real, quedó claro que la característica "2GIS for CarPlay" definitivamente sería. Es hora de hacer belleza.


Problemas de ventana gráfica


Era necesario configurar la ventana gráfica del mapa para dibujar rutas en el área sin controles innecesarios, y no solo en el medio. En resumen, para que se vea diferente:



Y entonces:



Esperaba obtener algún tipo de layoutGuide con el área visible actual. Para que tenga en cuenta la barra de navegación, la vista con la ruta y los controles en el mapa. De hecho, no obtuve nada. Todavía no está claro cómo configurar la ventana gráfica, por lo que tenemos un código duro como:


 let routeControlsWidth = self.view.frame.width * 0.48 let zoomControlWidth = self.view.frame.width * 0.15 

Construcción del pasaje no solo entre dos puntos


En la primera versión, decidimos tomar nuestro rubricator hecho a través de CPGridTemplate:



Favoritos y Casa / Trabajo a través de CPListTemplate.



Y búsqueda de teclado a través de CPSearchTemplate:



No mostraré el código sobre las plantillas, ya que es simple y la documentación está bien escrita (al menos sobre algo).


Sin embargo, vale la pena mencionar qué problemas se descubrieron al trabajar con ellos.

CPInterfaceController puede en navegación similar a UIKit. es decir


 self.interfaceController.pushTemplate(listTemplate, animated: true) self.interfaceController.presentTemplate(alertTemplate, animated: true) 

Pero si intenta ejecutar, por ejemplo, CPAlertTemplate, recibirá una afirmación en los registros de que CPAlertTemplate solo puede representarse modalmente.


No está claro por qué Apple no ocultó la lógica de los tramos bajo el capó sin haber creado una interfaz como:


 self.interfaceController.showTemplate(listTemplate, animated: true) 

También rompió la capacidad de usar los herederos de CPTemplate, como los controladores en UIKit.


Cuando intenta, por ejemplo, poner a su heredero en la pila de plantillas, obtiene esto:


 Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unsupported object <YourAwesomeGridTemplate: 0x60000060dce0> <identifier: 6CAC7E3B-FE70-43FC-A8B1-8FC39334A61D, userInfo: (null)> passed to pushTemplate:animated:. Allowed classes: {( CPListTemplate, CPGridTemplate, CPSearchTemplate, CPMapTemplate )}' 

Pruebas y errores


Probado por artemenko-aa . Uno de los primeros errores que encontró, todavía no podemos solucionarlo.


El hecho es que cuando desconecta el teléfono de la radio CarPlay, Watchdog nos atrapa esporádicamente, sin explicar la razón. Incluso syslogs abiertos, nada está claro. Entonces, si tiene una idea de cómo solucionar o entender la razón, no dude en comentar.


El siguiente error estaba en el mismo lugar, pero con un comportamiento especial. Escribí anteriormente que se llama al método didDisconnect de CPApplicationDelegate cuando se desconecta el teléfono de CarPlay. Y en este método, devolvemos la tarjeta de la pantalla de radio a la aplicación principal. Imagine cuántos problemas podríamos detectar si este método no se llamara al menos una vez de cada cinco.


Quedó claro que este es un problema de iOS, y no específicamente de nuestra aplicación, ya que todo el sistema creía que estaba conectado a CarPlay.



Incluso lo informé como radar (como todos los demás errores). Me pidieron que soltara registros con ese perfil, pero no pude responder al soporte durante algún tiempo, por lo que cerraron el radar.


Como Apple no planeaba hacer nada, el problema tuvo que ser evitado por sí solo, ya que se reproducía con bastante frecuencia.


Y luego recordé que la mayor parte de las conexiones con CarPlay pasa por Lightning. Esto significa que el teléfono se está cargando en el momento de la conexión, y en el momento de la desconexión, la carga cesa. Y si es así, puede suscribirse al estado de la batería y averiguar exactamente cuándo el teléfono dejó de cargarse y se desconectó de CarPlay.


El esquema es frágil, pero no teníamos otra opción. Fuimos por este camino, y funcionó!



Afortunadamente, esta muleta se ha eliminado por mucho tiempo del código: los desarrolladores de Apple arreglaron todo en una de las versiones de iOS.


La historia de dos editores.


La primera redirección estaba relacionada con los metadatos. El texto del editorial dice que nuestra descripción (no las notas de la versión) no dice que admitamos CarPlay. Como puede adivinar, ni la guía de revisión, ni el mismo Google Maps tenían esto. No discutimos (porque generalmente es más largo que editar los metadatos), copiamos la línea de las Notas de la versión a la Descripción y comenzamos a esperar una nueva revisión.


El segundo proyecto ocurrió debido a la lista de ciudades. 2GIS tiene una característica muy interesante: modo de operación fuera de línea completo. Esta característica nos disparó en la pierna.


Al conectar una aplicación sin una ciudad establecida a CarPlay, no mostramos el mapa, porque no hay nada que mostrar. Y para esto estábamos programados. La solución fue simple: una alerta sin botones, que dice que necesitas descargar la ciudad.



De qué no puedes hablar


Movimiento de mapa de gestos


Casi al mismo tiempo, salió el navegador bajo CarPlay de Google Maps, y allí puedes mover el mapa con gestos por la pantalla. API privadas, pensé, ¡esto es obvio! Los chicos de Google acaban de llegar de un edificio cercano y dijeron lo que necesitaban. Después de todo, la documentación dice:


 Navigation apps are designed to work with a variety of car input devices, and CarPlay does not support direct user interaction in the base view (apps do not directly receive tap or drag events). 

Sin embargo, todavía decidí asegurarme y busqué en Google, aunque era casi inútil, porque no había artículos técnicos sobre CarPlay Navigation Apps. Sin embargo, logré encontrar algo útil y, DE repente, en el sitio web de Apple .


En las pautas encontré un video que dice que la documentación está mintiendo descaradamente. El video muestra cómo aún puede arrastrar el mapa con gestos. Me di cuenta de que no entendía nada, y lo único que me quedaba era abrir CarPlay.framework y revisar todos los archivos .h.


¡Y he aquí! Encuentro en CPMapTemplate su delegado CPMapTemplateDelegate, en el que hay 3 métodos que parecen gritar que si los implementa, puede obtener el control de los gestos del mapa.


3 métodos

/ * Se llama cuando comienza un gesto panorámico. No se puede llamar cuando está conectado a algunos sistemas CarPlay.
/ /
función pública opcional mapTemplateDidBeginPanGesture (_ mapTemplate: CPMapTemplate)


/ * Se llama cuando cambia un gesto de desplazamiento. No se puede llamar cuando está conectado a algunos sistemas CarPlay.
/ /
función pública opcional mapTemplate (_ mapTemplate: CPMapTemplate, didUpdatePanGestureWith Traducción de traducción: CGPoint, velocidad: CGPoint)


/ * Se llama cuando finaliza un gesto de desplazamiento. No se puede llamar cuando está conectado a algunos sistemas CarPlay.
/ /
función pública opcional mapTemplate (_ mapTemplate: CPMapTemplate, didEndPanGestureWithVelocity velocidad: CGPoint
)


Los implementé y ejecuté la aplicación en un simulador, nada funcionó. Al no tener tiempo para estar molesto, me di cuenta de que el simulador puede ser de la misma calidad que la documentación y lo puse en el dispositivo. ¡Todo comenzó, la felicidad no conocía límites!


Dato curioso: una radio CarPlay necesita una cuarta parte de la pantalla para comprender que ha comenzado un gesto panorámico. Quiero señalar que UIPanGestureRecognizer solo necesita 10 puntos.


Uniformidad de IU en diferentes grabadoras de radio


Recibimos una apelación de soporte: el usuario solo tiene un rastreo más seguro en la búsqueda, aunque podría haber habido más. Pensé que es extraño, porque en todas las pantallas solo cabe una línea. Han solicitado una captura de pantalla:



Y esto es completamente diferente de la interfaz de usuario de CPSearchTemplate que mostré arriba. Y esto debe tenerse en cuenta durante el desarrollo, aunque todavía es imposible entender cuántas células en la placa de abajo pueden caber en la pantalla.


Control de límite de velocidad


Observamos las estadísticas y nos dimos cuenta de que usan el navegador para CarPlay y necesitamos llevarlo al menos al nivel del navegador en la aplicación principal. En primer lugar, decidimos agregar control de límite de velocidad. Por supuesto, hubo algunos problemas.


Pregunta número uno: ¿dónde colocar?


Revolviendo los archivos .h en CPWindow nuevamente, encontré un curioso diseño:
var mapButtonSafeAreaLayoutGuide: UILayoutGuide


Y eso resultó ser lo que necesitábamos. Nuestro control encaja perfectamente:




Pregunta número dos: ¿esto es generalmente legal?


El hecho es que técnicamente el control está en la vista base. Y la vista base de acuerdo con la documentación no puede contener nada excepto un mapa:


 The base view is where the map is drawn. The base view must be used exclusively to draw a map, and may not be used to display other UI elements. Instead, navigation apps overlay UI elements such as the navigation bar and map buttons using the provided templates. 

Pero los revisores nos extrañaron en la AppStore, lo que significa que los controles relacionados con la navegación aún pueden integrarse.


Búsqueda por voz




En el buen sentido, esta función tenía que hacerse antes que nada, pero hemos acumulado varias tareas de la deuda técnica que impidieron la implementación de la búsqueda por voz de CarPlay. Y esta tarea no fue tan simple como parecía.


El primer problema: animaciones. El hecho es que en CPVoiceControlTemplate no hay forma de hacer animaciones estándar. La animación para el reconocimiento de voz y la búsqueda se tuvo que recopilar cuadro por cuadro de las imágenes e indicar cuánto tiempo pasaron.


 for i in 1...12 { if let image = UIImage(named: "carplay_searching_\(i)") { images.append(image) } } let image = UIImage.animatedImage(with: images, duration: 0.96) 

Parece, como puede suponer, no realmente, pero no quiero inflar el tamaño de la aplicación.


El segundo problema: accesos. Las alertas para el acceso al micrófono y el reconocimiento de voz aparecen en la pantalla del teléfono. Tuve que escribir en la pantalla de la radio que el usuario necesita levantar el teléfono, dar permiso y solo luego usar el navegador en la radio. Muy comodo!


Coches con volante a la derecha.


¡Nos enviaron una captura de pantalla en la que la interfaz de usuario de toda la aplicación estaba al revés!



Y, por supuesto, la ventana gráfica del mapa siguió siendo la forma en que la codificamos, porque nadie esperaba que hubiera una configuración separada para los automóviles con volante a la derecha. No encontré cómo solucionar esto "correctamente", pero noté que, dado que nuestro control de límite de velocidad se encuentra en la Guía de diseño para los controles de mapa, se movió hacia el lado izquierdo.


Ultrafix no tardó en llegar. Lo hicieron groseramente, pero funciona.


 let isLeftWheelCar = self.speedControlViewController.view.frame.origin.x > self.view.frame.size.width / 2.0 

Realmente espero que haya una solución correcta, y simplemente no la leí.


Eso es todo para mí. Si de repente planea crear su propio navegador en CarPlay, tenga en cuenta que la documentación y el marco son imperfectos. La plataforma es completamente nueva, nadie sabe nada y Apple no tiene prisa por compartir conocimientos.

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


All Articles