
Salut Je m'appelle Vanya, j'écris une application mobile 2GIS pour iOS. Aujourd'hui, il y aura une histoire sur la façon dont notre navigateur est apparu dans CarPlay. Je vais vous expliquer comment, avec une telle documentation et des outils inachevés, nous avons créé un produit fonctionnel et l'avons placé dans l'AppStore.
Quelques mots sur CarPlay

Tout d'abord, un peu de matériel pour comprendre certains aspects de CarPlay et les raisons pour lesquelles nous avons pris certaines décisions.
CarPlay n'est pas un système d'exploitation dans un autre système d'exploitation, comme de nombreux articles l'écrivent. Si c'est grossier, alors CarPlay est un protocole pour travailler avec un affichage externe de l'écran de l'unité principale; le son des haut-parleurs de voiture; écrans tactiles, panneaux tactiles, rondelles et autres périphériques d'entrée.
C'est-à-dire que l'intégralité du code exécutable est situé directement dans l'application principale (pas même dans une extension séparée!) C'est très cool: pour obtenir de nouvelles fonctionnalités, vous n'avez pas besoin de mettre à jour la radio ou même la machine, il vous suffit de mettre à jour iOS.
Lors de la Keynote de la WWDC 2018, nous avons eu l'opportunité de créer des applications de navigation pour CarPlay, ce qui nous a fait très plaisir. Immédiatement après la présentation, nous avons envoyé une demande d'autorisation de développement pour CarPlay. Dans la demande, il fallait démontrer que notre application est capable de navigation.
Pendant que nous attendions une réponse d'Apple, il y a eu une conférence dans laquelle, en utilisant l'exemple d'application CountryRoads, a parlé de travailler avec CarPlay.framework. La conférence n'a pas parlé des pièges et des subtilités lorsque vous travaillez avec CarPlay, mais a mentionné qu'après la connexion à la radio CarPlay, l'application fonctionnera en mode arrière-plan.
Premier bâton dans les roues
L'application en arrière-plan nous a déçus. Il y avait deux raisons à cela:
- Nous ne travaillons pas en arrière-plan. Une fois quitté cette limitation pour des raisons techniques et pour des économies d'énergie.
- Notre carte est écrite en OpenGL (oui, obsolète, oui, pas Metal, nous le savons tous), et OpenGL en arrière-plan ne fonctionne pas. Au mieux, vous obtenez une vue noire, et au pire, un crash.
Il était encore possible de faire face au travail en arrière-plan, mais la carte devait définitivement être résolue. Puis l'idée est venue de passer par le MKMapView standard. Jusqu'à ce que vous commenciez à nous jeter des pierres sur l'idée d'utiliser des cartes Apple standard, je vais vous expliquer: nous allions utiliser MKMapView, mais pas les cartes Apple.
Le fait est que MKMapView peut charger des tuiles tierces. Les carreaux sont des conteneurs rectangulaires spéciaux pour les textures. Nous venons de devenir une servochka qui sait donner des tuiles. Il existe un code d' implémentation sur GitHub.
Réponse d'Apple
Nous avons reçu une réponse d'Apple, dans laquelle, en plus de l'autorisation de développer, nous avons également reçu de la documentation "pour l'élite", le code de l'exemple d'application CountryRoads (il a été montré lors de la conférence WWDC) et, surtout, la clé de capacité privée com.apple.developer.carplay-maps
. Cette clé est écrite dans le fichier de droits avec la valeur YES, afin que le système comprenne que vous pouvez traiter les événements de CarPlay au démarrage de votre application.
Sans attendre le sprint avec les histoires sélectionnées pour le développement, j'ai grimpé pour télécharger Xcode Beta. La première tentative de collecte de 2GIS a été un échec. Mais l'exemple de projet d'application CoutryRoads a pu être assemblé pour le simulateur.
Avant chaque ouverture de la fenêtre du simulateur CarPlay, ce dernier devait être personnalisé via une telle fenêtre:

Pour ce faire, vous deviez écrire la ligne dans le terminal: par defaults write com.apple.iphonesimulator CarPlayExtraOptions -bool YES
Pour une raison quelconque, cela n'a pas fonctionné - j'ai dû l'exécuter sur presque le plus petit simulateur avec une résolution de 800 × 480 points et une échelle × 2. Ce paramètre fonctionne actuellement et aide beaucoup.
Après avoir créé mon exemple de projet et armé de documentation, j'ai commencé à comprendre ce qui se passait.
La première chose que j'ai réalisée: les applications de navigation pour CarPlay se composent de vues de base et de couches de modèles.

La vue de base est votre carte. Sur cette couche, il ne devrait y avoir qu'une carte, pas d'autres vues et contrôles.
Les modèles sont un ensemble obligatoire presque non personnalisable d'éléments d'interface utilisateur pour afficher les itinéraires, les manœuvres, toutes sortes de listes, etc.
Développement bêta
Passons à l'écriture de code. La première chose à faire est d'implémenter quelques méthodes CPApplicationDelegate requises dans le fichier ApplicationDelegate.
func application( _ application: UIApplication, didConnectCarInterfaceController controller: CPInterfaceController, to window: CPWindow ) {} func application( _ application: UIApplication, didDisconnectCarInterfaceController controller: CPInterfaceController, from window: CPWindow ) {}
Regardons la signature:
Avec UIApplication, tout est clair.
CPWindow est le successeur de UIWindow, une fenêtre pour l'affichage externe de l'unité principale de la radio.
CPInterfaceController - quelque chose comme un analogue de UINavigationController, uniquement à partir de CarPlay.framework.
Nous passons maintenant directement à la mise en œuvre de la méthode.
func application( _ application: UIApplication, didConnectCarInterfaceController controller: CPInterfaceController, to window: CPWindow ) { let carMapViewController = CarMapViewController( interfaceController: controller ) let navigationController = UINavigationController( rootViewController: carMapViewController ) window.rootViewController = navigationController }
Dans didConnect, vous devez écrire du code similaire à celui que nous avions l'habitude de voir dans didFinishLaunching. CarMapViewController est une vue de base (le contrôleur est en fait, mais ok), selon la documentation.
Voici l'image que j'ai finalement obtenue:

Quelque part à ce moment, il m'est apparu que le nouveau système de nouvelle construction Xcode est activé par défaut et, très probablement, à cause de cela, 2GIS ne le fera pas.
J'ai ouvert Xcode, installé un système de construction hérité (ou plutôt stable, appelons un chat un chat), et ma théorie a été confirmée: 2GIS a été assemblé.
Après avoir défini la même clé de capacité, j'ai lancé 2GIS sous CarPlay et je n'ai vu aucun journal sur le passage de l'application en mode arrière-plan. Cela est devenu encore plus incompréhensible, car les ingénieurs d'Apple de la scène ont parlé du mode d'arrière-plan, mais, d'autre part, ils nous ont promis une contentView d'UIAlertView, et en conséquence, UIAlertView est devenu obsolète.
Ayant décidé qu'il devrait en être ainsi, je n'ai pas pris la peine de MKMapView. Cela nous priverait hors ligne et nous ferait réécrire le rendu des routes.
Problème de carte unique
Je n'ai pas eu le temps de me réjouir de la nouvelle que CarPlay aura notre carte, car le problème suivant m'a confronté: en raison des caractéristiques techniques, il ne peut y avoir qu'une seule carte.
Une solution rapide à ce problème a été, bien que pas très élégante.
Habituellement, lorsque vous utilisez 2GIS sur CarPlay, le téléphone est verrouillé et se trouve quelque part sur l'étagère. Donc, la carte en ce moment sur le téléphone n'est pas vraiment nécessaire (cela ne fera pas de mal à chercher, bien sûr). Par conséquent, lorsque nous avons connecté le téléphone à CarPlay, nous avons décidé de retirer la carte de l'application principale et de l'afficher sur l'écran CarPlay de la radio. Et une fois déconnecté, revenez à l'application sur le téléphone.
Oui, c'est une solution en soi, mais c'est rapide, cela fonctionne toujours et n'a pas eu besoin de lancer quelques autres commandes pour riveter MVP.
Contrôles sur la carte
Donc, nous avons obtenu notre carte sur l'écran de la radio. Maintenant, il était nécessaire de faire les premières choses évidentes pour n'importe quelle carte: les commandes de zoom, l'emplacement actuel et le mouvement de la carte.

Commençons par le zoom et l'emplacement actuel, car ces contrôles sont situés sur la carte elle-même et ce ne sont pas des UIControl ordinaires. Comme je l'ai écrit ci-dessus, seule la carte est sur la vue de base.
Afin de placer ces contrôles sur la carte, j'ai dû aller à nouveau dans la documentation et l'exemple d'application. Là, j'ai lu sur le premier modèle - CPMapTemplate.

CPMapTemplate - un modèle transparent pour afficher certains contrôles sur la carte et l'analogue de la barre de navigation. Il est créé et configuré comme ceci:
let mapTemplate = CPMapTemplate() self.interfaceController.setRootTemplate(mapTemplate, animated: false)
Ensuite, vous devez créer ces contrôles et les mettre sur la carte.
let zoomInButton = CPMapButton(…) let zoomOutButton = CPMapButton(…) let myLocationButton = CPMapButton(…) self.mapTemplate.mapButtons = [ zoomInButton, zoomOutButton, myLocationButton ]
Mais le tableau mapButtons s'est avéré drôle, car peu importe le nombre d'éléments que vous y mettez, il ne prendra que les trois premiers éléments et les affichera à l'écran. Vous ne recevrez aucune erreur dans le journal ou les assertions.
Ensuite, j'ai pu voir comment je peux faire bouger la carte, et je l'ai trouvé dans la documentation:
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).
Étrange, ai-je pensé, et j'ai pu voir comment cela se faisait dans l'exemple d'application CountryRoads. La réponse passe par cette interface:

Pas très pratique, mais d'une manière différente, la documentation ne mentira pas, non?
Depuis la place des contrôles sur la carte que nous avions épuisée, il a fallu faire un bouton pour mettre la carte en mode "drag" dans cet analogue de navigationBar.
let panButton = CPBarButton(…) self.mapTemplate.leadingNavigationBarButtons = [panButton] self.mapTemplate.trailingNavigationBarButtons = []
Mais les tableaux des principauxNavigationBarButtons et trailingNavigationBarButtons n'étaient pas sans plaisanterie: combien d'éléments en eux poussent, ils ne prendront que les deux premiers. Aussi sans erreurs dans le journal et les assertions.
Et pour activer et désactiver le mode glisser-déposer de la carte, vous devez écrire:
self.mapTemplate.showPanningInterface(animated: true) self.mapTemplate.dismissPanningInterface(animated: true)
Création et affichage d'itinéraires sur une carte
Ensuite, j'ai commencé à réutiliser notre API existante pour créer des itinéraires.
Juste pour une démonstration et comprendre quoi et comment faire, j'ai décidé de prendre deux points et de construire un itinéraire entre eux. Le point A était l'emplacement de l'utilisateur et le point B était notre bureau principal à Novossibirsk.
Code 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 )
Sur l'écran, nous avons obtenu un contrôle avec une description de l'itinéraire:

Mode navigation
Les itinéraires sont bons, mais la principale caractéristique du navigateur est la navigation. Pour qu'il apparaisse, vous devez écrire ce qui suit:
func mapTemplate( _ mapTemplate: CPMapTemplate, startedTrip trip: CPTrip, using routeChoice: CPRouteChoice ) { self.navigationSession = self.mapTemplate.startNavigationSession(for: trip) }
CPNavigationSession - une classe avec laquelle vous pouvez afficher certains éléments d'interface utilisateur qui sont nécessaires uniquement en mode navigation.
Pour afficher la manœuvre, vous devez:
let maneuver = CPManeuver() maneuver.symbolSet = CPImageSet( lightContentImage: icon, darkContentImage: darkIcon ) maneuver.instructionVariants = [". "] maneuver.initialTravelEstimates = CPTravelEstimates(…) self.navigationSession?.upcomingManeuvers = [maneuver]
Ensuite, sur l'écran de la radio, nous obtenons ceci:

Pour mettre à jour la séquence à manoeuvrer, vous devez:
let estimates = CPTravelEstimates(…) self.navigationSession?.updateEstimates(estimates, for: maneuver)
Ça marche!
Lorsque la fonctionnalité de base du navigateur était prête, j'ai décidé de montrer ce métier dans une présentation interne. La présentation a été un succès: tout le monde a eu l'idée de terminer, tester et lancer le navigateur dans les plus brefs délais.
Tout d'abord, nous avons commandé une véritable unité centrale avec prise en charge de CarPlay. Et puis, comme on dit, la chaleur a commencé.

Profils de provision
En raison de l'ajout d'une nouvelle clé de capacité, les profils doivent être régénérés. En développement normal, nous n'y pensons pas, car Xcode fera tout par lui-même. Mais pas dans le cas d'une clé privée.
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.
Cela a également brisé notre CI, car pour la distribution locale des versions d'application, nous utilisons un compte d'entreprise, dans lequel nous n'avons pas demandé l'autorisation de développer l'application pour CarPlay. Mais c'est une histoire complètement différente.
Débogage
Vous pouvez vous connecter à CarPlay via Bluetooth ou Lightning. La pratique montre que la deuxième méthode est beaucoup plus populaire. Notre radio en Bluetooth ne savait pas comment, donc pendant le développement j'ai dû utiliser le débogage Wi-Fi. Si vous l'avez essayé sur des projets plus difficiles que bonjour, alors vous savez ce que c'est que l'enfer.
Et pour ceux qui n'ont pas essayé, je dis:J'ai récupéré l'application par fil sur le téléphone, puis seulement, en connectant le téléphone à CarPlay, via Wi-Fi, je l'ai téléchargée sur le téléphone et l'ai exécutée pendant plusieurs minutes.
La copie de l'application sur le téléphone a duré environ 3 minutes, le lancement de l'application pendant environ une minute, et ce n'est qu'après avoir démarré l'arrêt aux points d'arrêt que 15 secondes plus tard.
Et puis, il m'est devenu très intéressant de savoir pourquoi Apple n'a pas créé de DevKit (pour que Apple-way, ça marche et c'est tout). Il n'était pas très pratique d'assembler un banc d'essai sans lui. Jusqu'à présent, une fois toutes les deux semaines, quelque chose tombe - vous devez vous souvenir des photos dans quoi vous tenir. Il est bon que l'administrateur, lors de l'assemblage de ce support, ait dit quoi et pourquoi.
Le meilleur cadre que nous ayons jamais créé
En fin de compte, lorsque tout a été assemblé sur un véritable appareil, il est devenu clair que la fonctionnalité «2GIS for CarPlay» le serait certainement. Il est temps de faire de la beauté.
Problèmes de fenêtre
Il était nécessaire de configurer la fenêtre de la carte pour tracer des itinéraires dans la zone sans contrôles inutiles, et pas seulement au milieu. En bref, pour le rendre différent:

Et donc:

J'espérais obtenir une sorte de layoutGuide avec la zone visible actuelle. Pour qu'il prenne en compte la barre de navigation, la vue avec l'itinéraire et les commandes sur la carte. En fait, je n'ai rien reçu. On ne sait toujours pas comment configurer la fenêtre, nous avons donc un code dur comme:
let routeControlsWidth = self.view.frame.width * 0.48 let zoomControlWidth = self.view.frame.width * 0.15
Construction du passage non seulement entre deux points
Dans la première version, nous avons décidé de prendre notre rubricator fait via CPGridTemplate:

Favoris et domicile / travail via CPListTemplate.

Et la recherche au clavier via CPSearchTemplate:

Je ne montrerai pas le code sur les modèles, car il est simple et la documentation à ce sujet est bien écrite (au moins sur quelque chose).
Cependant, il convient de mentionner les problèmes découverts lors de leur utilisation.CPInterfaceController peut en navigation similaire à UIKit. c'est-à-dire
self.interfaceController.pushTemplate(listTemplate, animated: true) self.interfaceController.presentTemplate(alertTemplate, animated: true)
Mais si vous essayez d'exécuter, par exemple, CPAlertTemplate, vous obtiendrez des assertions dans les journaux que CPAlertTemplate ne peut être représenté que de façon modale.
On ne sait pas pourquoi Apple n'a pas caché la logique des tranches sous le capot sans avoir fait une interface comme:
self.interfaceController.showTemplate(listTemplate, animated: true)
Il a également rompu la possibilité d'utiliser les héritiers de CPTemplate, comme les contrôleurs dans UIKit.
Lorsque vous essayez, par exemple, de mettre votre héritier sur la pile de modèles, vous obtenez ceci:
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 )}'
Tests et bogues
Testé par artemenko-aa . L'un des premiers bugs qu'il a trouvé, nous ne pouvons toujours pas le corriger.
Le fait est que lorsque vous déconnectez le téléphone de la radio CarPlay, Watchdog nous cloue sporadiquement - sans expliquer la raison. Même syslog ouvert, rien n'est clair. Donc, si vous avez une idée de comment résoudre ou comprendre la raison, n'hésitez pas à commenter.
Le bogue suivant était au même endroit, mais avec un comportement spécial. J'ai écrit ci-dessus que la méthode didDisconnect de CPApplicationDelegate est appelée lorsque le téléphone est déconnecté de CarPlay. Et dans cette méthode, nous renvoyons la carte de l'écran radio à l'application principale. Imaginez combien de problèmes nous rencontrerions si cette méthode n'était pas appelée au moins une fois sur cinq.
Il est devenu clair qu'il s'agit d'un problème d'iOS, et pas spécifiquement de notre application, car tout le système pensait qu'il était connecté à CarPlay.

Je l'ai même signalé comme radar (comme tous les autres bugs). On m'a demandé de déposer des journaux avec un tel profil, mais je n'ai pas pu répondre au support pendant un certain temps, alors ils ont fermé le radar.
Comme Apple n'avait pas l'intention de faire quoi que ce soit, le problème a dû être contourné de lui-même, car il était reproduit assez souvent.
Et puis je me suis souvenu que la part du lion des connexions à CarPlay passe par Lightning. Cela signifie que le téléphone est en charge au moment de la connexion et qu'au moment de la déconnexion, le chargement cesse. Et si c'est le cas, vous pouvez vous abonner à l'état de la batterie et savoir exactement quand le téléphone a cessé de se charger et s'est déconnecté de CarPlay.
Le schéma est fragile, mais nous n'avions pas le choix. Nous sommes passés par là et ça a marché!

Heureusement, cette béquille a longtemps été supprimée du code: les développeurs Apple ont tout corrigé dans l'une des versions iOS.
L'histoire de deux éditeurs
La première redirection était liée aux métadonnées. Le texte de l'éditorial indique que notre description (et non les notes de version) ne dit pas que nous soutenons CarPlay. Comme vous pouvez le deviner, ni la directive d'examen, ni le même Google Maps ne l'avaient. Nous ne nous sommes pas disputés (car c'est généralement plus long que la modification des métadonnées), nous avons copié la ligne des notes de publication vers la description et avons commencé à attendre une nouvelle révision.
Le deuxième projet s'est produit à cause de la liste des villes. 2GIS a une fonctionnalité très intéressante - un mode de fonctionnement hors ligne complet. Cette fonction nous a tiré dans la jambe.
Lors de la connexion d'une application sans ville établie à CarPlay, nous ne montrons pas la carte, car il n'y a rien à afficher. Et pour cela, nous étions programmés. La solution était simple: une alerte sans boutons, qui indique que vous devez télécharger la ville.

De quoi tu ne peux pas parler
Mouvement de la carte des gestes
Vers la même époque, le navigateur sous CarPlay de Google Maps est sorti - et là, vous pouvez déplacer la carte avec des gestes sur l'écran. Les API privées, je pensais, c'est évident! Les gars de Google viennent juste d'un immeuble voisin et ont dit ce dont ils avaient besoin. Après tout, la documentation dit:
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).
Cependant, j'ai quand même décidé de m'en assurer et j'ai été googlé, bien que ce soit presque inutile, car il n'y avait pas d'articles techniques sur les applications de navigation CarPlay. Cependant, j'ai réussi à trouver quelque chose d'utile et, SOUDAINEMENT, sur le site d'Apple .
Dans les directives, j'ai trouvé une vidéo qui dit que la documentation ment impudemment. La vidéo montre comment vous pouvez toujours faire glisser la carte avec des gestes. J'ai réalisé que je ne comprenais rien, et la seule chose qui me restait était d'ouvrir CarPlay.framework et de revoir tous les fichiers .h.
Et voilà! Je trouve dans CPMapTemplate son délégué CPMapTemplateDelegate, dans lequel il y a 3 méthodes qui semblent crier que si vous les implémentez, vous pouvez prendre le contrôle des gestes de la carte.
3 méthodes/ * Appelé lorsqu'un mouvement de panoramique commence. Ne peut pas être appelé lorsqu'il est connecté à certains systèmes CarPlay.
/
fonction publique facultative mapTemplateDidBeginPanGesture (_ mapTemplate: CPMapTemplate)
/ * Appelé lorsqu'un mouvement de panoramique change. Ne peut pas être appelé lorsqu'il est connecté à certains systèmes CarPlay.
/
option publique func mapTemplate (_ mapTemplate: CPMapTemplate, didUpdatePanGestureWith Traduction de traduction: CGPoint, vélocité: CGPoint)
/ * Appelé à la fin d'un mouvement de panoramique. Ne peut pas être appelé lorsqu'il est connecté à certains systèmes CarPlay.
/
fonction publique facultative mapTemplate (_ mapTemplate: CPMapTemplate, didEndPanGestureWithVelocity velocity: CGPoint
)
Je les ai implémentés et j'ai exécuté l'application sur un simulateur - rien n'a fonctionné. N'ayant pas le temps de m'énerver, je me suis rendu compte que le simulateur peut être de la même qualité que la documentation, et le mets sur l'appareil. Tout a commencé, le bonheur n'a pas de limites!
Fait amusant: une radio CarPlay a besoin d'un quart de l'écran pour comprendre qu'un mouvement de panoramique a commencé. Je veux noter que UIPanGestureRecognizer n'a besoin que de 10 points.
Uniformité de l'interface utilisateur sur différents magnétophones radio
Nous avons reçu un appel à l'appui: l'utilisateur n'a qu'un seul sajest rampant dans la recherche, bien qu'il puisse y en avoir plus. C'est étrange, pensais-je, car sur tous les écrans, une seule ligne convient. Ont demandé une capture d'écran:

Et cela est complètement différent de l'interface utilisateur de CPSearchTemplate que j'ai montrée ci-dessus. Et cela doit être pris en compte lors du développement, bien qu'il soit toujours impossible de comprendre combien de cellules dans la plaque ci-dessous peuvent tenir sur l'écran.
Contrôle de limite de vitesse
Nous avons examiné les statistiques et réalisé qu'elles utilisent le navigateur pour CarPlay et nous devons l'amener au moins au niveau du navigateur dans l'application principale. Tout d'abord, nous avons décidé d'ajouter un contrôle de limitation de vitesse. Bien sûr, il y avait des problèmes.
Question numéro un: où placer?
En fouillant à nouveau autour des fichiers .h dans CPWindow, j'ai trouvé une mise en page curieuse
var mapButtonSafeAreaLayoutGuide: UILayoutGuide
Et cela s'est avéré être ce dont nous avions besoin. Notre contrôle s'intègre parfaitement:


Question numéro deux: est-ce généralement légal?
Le fait est que techniquement le contrôle est sur la vue de base. Et la vue de base selon la documentation ne peut pas contenir autre chose qu'une carte:
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.
Mais les examinateurs nous ont manqué dans l'AppStore, ce qui signifie que les contrôles liés à la navigation peuvent toujours être intégrés.
Recherche vocale


Dans le bon sens, cette fonctionnalité devait d'abord être effectuée, mais nous avons accumulé plusieurs tâches de la dette technique qui ont empêché la mise en œuvre de la recherche vocale pour CarPlay. Et cette tâche n'était pas aussi simple qu'il y paraissait.
Le premier problème: les animations. Le fait est que dans CPVoiceControlTemplate il n'y a aucun moyen de faire des animations standard. Les animations pour la reconnaissance et la recherche de la parole devaient être collectées image par image à partir des images et indiquer le temps passé.
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)
Il semble, comme vous pouvez le deviner, pas vraiment, mais je ne veux pas augmenter la taille de l'application.
Le deuxième problème: les accès. Des alertes d'accès au microphone et de reconnaissance vocale s'affichent sur l'écran du téléphone. J'ai dû écrire sur l'écran de la radio que l'utilisateur devait décrocher le téléphone, donner la permission et ensuite seulement utiliser le navigateur sur la radio. Très confortable!
Voitures à conduite à droite.
On nous a envoyé une capture d'écran dans laquelle l'interface utilisateur de l'ensemble de l'application a été bouleversée!

Et, bien sûr, la fenêtre d'affichage de la carte est restée la façon dont nous l'avons codée en dur, car personne ne s'attendait à ce qu'il y ait un paramètre distinct pour les voitures avec conduite à droite. Je n'ai pas trouvé comment contourner cela «correctement», mais j'ai remarqué que, puisque notre contrôle de limite de vitesse se trouve dans le layoutGuide pour les contrôles de carte, il s'est déplacé vers la gauche.
Ultrafix ne tarda pas à venir. Ils l'ont fait grossièrement, mais cela fonctionne.
let isLeftWheelCar = self.speedControlViewController.view.frame.origin.x > self.view.frame.size.width / 2.0
J'espère vraiment qu'il existe une bonne solution et je ne l'ai tout simplement pas lue.
C’est tout pour moi.Si vous envisagez soudainement de créer votre propre navigateur sous CarPlay, gardez à l'esprit que la documentation et le cadre sont imparfaits. La plateforme est complètement nouvelle, personne ne sait rien, et Apple n'est pas pressé de partager ses connaissances.