
Utilisez la personnalisation de la mise en page pour contrôler la hiérarchie des vues et l'emplacement à l'écran des vues fournies par Google et de vos propres vues métier personnalisées.
Au lieu d'ajouter des vues personnalisées dans des emplacements de mise en page prédéfinis, implémentez un délégué de mise en page pour gérer la mise en page de l'ensemble de l'écran. Lors des transitions d'état de navigation, votre délégué de mise en page reçoit les composants fournis par Google, tels que la carte de changement de direction, la carte d'heure d'arrivée prévue et les boutons. Positionnez ces éléments à côté de votre propre contenu personnalisé à l'aide des contraintes de mise en page automatique standards d'iOS.
Utilisez ce framework pour positionner précisément à l'écran des informations personnalisées sur l'entreprise (par exemple, l'état de la commande ou les instructions de retrait) tout en évitant que vos vues ne se chevauchent.
Fonctionnement de la personnalisation de la mise en page
La personnalisation de la mise en page utilise un modèle de conception de délégation. Au lieu de dessiner ou de positionner automatiquement les composants de l'interface utilisateur à l'écran, le SDK les transmet directement à une classe personnalisée que vous écrivez : le délégué de mise en page.
Pour personnaliser votre mise en page, créez une classe conforme au protocoleNavigationLayoutDelegate et transmettez une instance à la méthode setLayoutDelegate(_:) de votre GMSMapView. Chaque fois que l'état de navigation change (par exemple, lorsque vous passez de la carte de base à la navigation détaillée active), le SDK exécute les méthodes de rappel sur votre délégué et fournit les composants d'interface utilisateur disponibles pour cet état spécifique.
Pour offrir une expérience utilisateur fluide et intégrée, ce framework établit une séparation claire des responsabilités entre votre application et le SDK. Utilisez le délégué de mise en page pour personnaliser les éléments suivants :
- Créer la hiérarchie des vues : choisissez exactement les composants Google et les vues métier personnalisées à ajouter à l'écran pour chaque état de l'UI.
- Positionnez chaque élément : définissez des ancrages d'écran, des marges et un emplacement de mise en page précis. Évitez d'appliquer des contraintes de largeur ou de hauteur personnalisées aux composants Google, car ils calculent leurs propres dimensions internes.
- Cadrer la carte de base : définissez les limites visibles de la caméra de la carte à l'aide des coordonnées du composant de la fenêtre d'affichage.
- Superposer l'écran : décidez si les vues personnalisées doivent flotter au-dessus, en dessous ou à côté des commandes intégrées de Google.
En revanche, vous ne pouvez pas personnaliser les composants Google suivants avec le délégué de mise en page :
- Dimensions des composants : taille et dimensions internes des composants fournis par Google, que le SDK calcule automatiquement.
- Conditions de déclenchement : lorsque des alertes ou des invites dynamiques s'affichent en fonction des données d'itinéraire en temps réel.
Principes d'implémentation
Lorsque vous écrivez un délégué de mise en page, tenez compte des règles suivantes pour éviter les bugs de mise en page ou les plantages d'exécution :
- Par défaut, l'écran est vide : les composants fournis par Google n'apparaissent que si votre délégué les ajoute explicitement à la hiérarchie des vues et les positionne.
- Les anciennes API ne sont pas compatibles : les propriétés et les méthodes qui contrôlent l'ancien modèle de mise en page basé sur des emplacements ne sont pas compatibles et peuvent ne pas se comporter comme prévu lorsqu'un délégué personnalisé est actif.
- Ne modifiez pas les structures de vue internes : ne parcourez pas et ne modifiez pas les hiérarchies de vues des composants fournis par Google (comme la carte de changement de direction ou la carte d'heure d'arrivée prévue). Étant donné que ces hiérarchies de vues internes sont des détails d'implémentation sous-jacents, elles sont susceptibles de changer d'une version du SDK à l'autre. Si vous les modifiez, votre mise en page risque de ne plus fonctionner lors des futures mises à jour du SDK.
Compatibilité avec l'ancienne API
Pour un comportement de mise en page fiable lorsque vous utilisez un délégué de mise en page personnalisé, évitez d'utiliser les anciennes API basées sur des emplacements suivantes, qui seront bientôt obsolètes. Pour migrer une application existante, remplacez l'utilisation de ces API par du code dans votre délégué de mise en page personnalisé :
Afficher les anciennes API basées sur les emplacements
| API Legacy | Remplacement de Layout Delegate |
|---|---|
isNavigationHeaderEnabled |
Lisez la vue turnCard dans startGuidance(navMap:newState:oldState:). |
isNavigationFooterEnabled |
Lisez la vue etaCard dans startGuidance(navMap:newState:oldState:). |
isNavigationTripProgressBarEnabled |
Lisez la vue tripProgressBar dans startGuidance(navMap:newState:oldState:). |
isNavigationReportIncidentButtonEnabled |
Recherchez le bouton .reporting dans le tableau buttons. |
setHeaderAccessoryView(_:) et invalidateLayout(forAccessoryView:) |
Ajoutez votre propre vue à la mise en page et ancrez-la à turnCard. |
padding et paddingAdjustmentBehavior |
Positionnez la vue viewport pour cadrer la caméra. |
navigationHeaderLayoutGuide |
Ancrez vos vues personnalisées directement dans turnCard. |
navigationFooterLayoutGuide |
Ancrez vos vues personnalisées directement dans etaCard. |
bottomTrailingButtonsLayoutGuide |
Ancrez vos boutons directement aux repères du etaCard ou du conteneur. |
bottomTrailingButtonsAlternateLayoutGuide |
Ancrez vos boutons directement aux contraintes du bouton .reporting ou de wideMode. |
navigatorWillPresentPrompt(_:) et navigatorDidDismissPrompt(_:) |
Fiez-vous aux rappels de vos requêtes NavigationLayoutDelegate, tels que showPrompt(navMap:newPrompt:). |
Lorsqu'un délégué de mise en page personnalisée est défini, les guides de mise en page anciens (tels que navigationHeaderLayoutGuide ou bottomTrailingButtonsLayoutGuide) et les méthodes d'affichage des accessoires d'en-tête n'ont aucun effet. Ancrez plutôt vos vues sur celles fournies par Google.
La propriété compassButton de GMSUISettings contrôle toujours la boussole lorsque l'UI de navigation est désactivée. Lorsque la navigation est activée, votre délégué décide d'afficher ou non la boussole à partir du tableau buttons.
Checklist Essentials
Suivez ces étapes et exigences essentielles pour implémenter correctement votre délégué de mise en page :
-
Définissez le délégué avant d'activer la navigation : appelez
setLayoutDelegate(_:)sur votreGMSMapViewavant d'activer la navigation avecisNavigationEnabledouenableNavigation(with:). La méthode génère une erreur si la navigation est déjà activée ou si vous avez créé la vue de carte pour un écran CarPlay. La vue de la carte conserve une référence forte à votre délégué. To restore the built-in layout, passnilwhile navigation is disabled.// Set the layout delegate before you enable navigation. do { try mapView.setLayoutDelegate(MyLayoutDelegate()) } catch { print("Couldn't set the layout delegate: \(error)") } mapView.isNavigationEnabled = true
-
Associer les vues obligatoires : vous devez ajouter le logo Google (
googleLogo) et la fenêtre d'affichage (viewport) à votre conteneur de mise en page dans tous les états de navigation. -
Respecter les dimensions fixes : le SDK détermine les dimensions de plusieurs composants fournis par Google. N'appliquez pas de contraintes de largeur ou de hauteur personnalisées aux éléments suivants :
- Carte de navigation
- carte ETA
- Requêtes entrantes
- Logo Google
- Widget de vitesse
-
Ajouter des éléments en tant que sous-vues : ajoutez les vues fournies par Google directement en tant que sous-vues à votre vue de conteneur personnalisée ou à la
GMSMapViewelle-même. -
Aligner à l'aide des guides de mise en page : les vues fournies par Google incluent des marges visuelles intégrées. Pour aligner parfaitement vos éléments personnalisés avec les composants fournis par Google, limitez vos vues à la propriété
layoutMarginsGuidede la vue Google. -
Nettoyage à la sortie de l'état : dans
stopGuidance(navMap:newState:oldState:), supprimez les vues de guidage actives et désactivez leurs contraintes avant de restaurer votre mise en page prête pour la navigation. DansdisableNavigationUI(navMap:oldState:), supprimez toutes les vues et tous les guides de mise en page que vous avez ajoutés, y compris les invites à l'écran. Cela évite que les vues d'un état ne chevauchent celles de l'état suivant. -
Propre accessibilité et compatibilité avec l'écriture de droite à gauche : les vues fournies par Google respectent les normes d'accessibilité, mais votre délégué est responsable de l'accessibilité de la mise en page globale. Pour prendre en charge les langues de droite à gauche, utilisez des ancres de début et de fin au lieu d'ancres de gauche et de droite.
-
Évitez les anciennes API de mise en page : n'utilisez pas les anciennes propriétés de modification de la mise en page sur
GMSUISettingsni les anciens guides de mise en page surGMSMapViewlorsqu'un délégué personnalisé est actif. Le SDK peut ignorer ces anciens appels lorsqu'un délégué personnalisé est associé.
États de l'UI et composants Google
Lorsqu'une transition d'état de navigation se produit, le SDK transmet un objet NavigationUIState en lecture seule à votre rappel de délégué. Cet objet regroupe les options de configuration de la mise en page actuelle avec les composants Google (par exemple, les instances UIView) dont vous avez besoin pour afficher l'écran.
Le délégué de mise en page gère la hiérarchie des vues dans quatre états opérationnels. Le diagramme d'état suivant illustre la transition du SDK entre les états de navigation et les méthodes de rappel qu'il exécute sur votre délégué :
Les composants Google spécifiques fournis à votre délégué dépendent de la phase actuelle du cycle de vie de la navigation.
Composants obligatoires (tous les états de navigation)
Quelle que soit la phase de navigation actuelle, vous devez inclure, positionner et maintenir visibles les composants Google suivants dans votre hiérarchie des vues :
Logo Google (
googleLogo) : ce composant affiche le logo Google Maps obligatoire. Si le bouton Recentrer est activé, le logo se transforme automatiquement en ce bouton lorsque le conducteur fait défiler la carte pour l'éloigner de son véhicule. Par conséquent, l'emplacement du logo détermine l'endroit où le bouton Recentrer s'affiche. Nous vous recommandons de placer le logo dans l'angle inférieur gauche de votre mise en page pour respecter les attentes standards de l'UI de la carte.Fenêtre d'affichage (
viewport) :UIViewinvisible qui définit les limites de cadrage de la caméra. L'emplacement de la fenêtre d'affichage détermine exactement l'endroit où le SDK centre le chevron du véhicule et trace la ligne de l'itinéraire actif. Positionnez la fenêtre d'affichage de manière à ce qu'elle couvre la zone ouverte et non masquée de l'écran, en évitant les superpositions opaques telles que les bottom sheets personnalisées.
Composants prêts pour la navigation
Pendant l'état "Prêt pour la navigation", l'UI reste minimale pour que l'utilisateur se concentre sur la carte de base. Votre objet NavigationReadyUIState permet d'accéder aux éléments suivants :
- buttons : tableau de vues d'actions flottantes configurées pour la carte de base (généralement, uniquement le bouton "Boussole"). Étant donné que Google peut ajouter ou réorganiser des boutons dans les futures versions du SDK, évitez de vous fier à des index de tableau fixes. À la place, parcourez le tableau, lisez le type de chaque bouton en comparant sa propriété
typeàNavigationButtonKnownType.compass, puis extrayez la vue physique en appelant sa propriétéview. Notez que les boutons exacts de ce tableau diffèrent de ceux disponibles pendant le guidage actif.
composant de guidage actif
Lorsque la navigation détaillée commence, le SDK déverrouille l'ensemble des commandes de navigation. Votre objet ActiveGuidanceUIState permet d'accéder aux composants Google suivants :
turnCard: bannière d'en-tête principale affichant les instructions de manœuvre à venir, les mesures de distance et l'aide au choix de la voie. Placez-la en haut de votre mise en page pour établir une hiérarchie de navigation familière et ancrer vos vues personnalisées autour d'elle.etaCard: bannière de pied de page affichant l'heure d'arrivée estimée, la durée restante du trajet et la distance restante jusqu'à la destination. Placez-le en bas de l'écran ou intégrez ses coordonnées à vos feuilles de gestion des tâches personnalisées.tripProgressBar: barre de progression verticale qui indique au conducteur la distance parcourue sur l'itinéraire actuel. Contrairement aux anciennes mises en page qui fixaient strictement ce bouton au bord de la carte, vous pouvez l'ancrer n'importe où, par exemple le long d'un bord latéral de votre conteneur personnalisé.speedWidget: commande flottante pour le compteur de vitesse et les limitations de vitesse affichées. En fonction des paramètres de votre API (les propriétésshouldDisplaySpeedometeretshouldDisplaySpeedLimitdeGMSMapView) et de la disponibilité des données, cette vue se redimensionne de manière dynamique entre quatre états visuels au moment de l'exécution (affichage de rien, de la vitesse actuelle uniquement, de la limitation de vitesse uniquement ou des deux valeurs). Comme le widget peut passer d'une taille à l'autre sans préavis, ancrez toujours les vues environnantes à l'aide de contraintes afin que votre mise en page s'adapte automatiquement pour éviter les chevauchements spatiaux.
buttons: tableau développé de vues d'actions flottantes pour l'état de guidage actif (incluant généralement à la fois la boussole et le bouton de signalement d'incident). Comme dans l'état "Prêt pour la navigation", vous pouvez localiser et extraire des vues de boutons individuelles en filtrant le tableau partype(.compassou.reporting) et en extrayant la vue physique à l'aide de sa propriétéview. Vous pouvez ensuite les positionner indépendamment ou les ajouter à unGMSNavigationHidingStackView. Cette vue de pile organise les boutons verticalement et, lorsque l'espace vient à manquer, les masque en commençant par la fin de son tableauarrangedSubviews. Ne définissez pasisHiddensur les vues qu'il gère.
NavigationStyleValues.headerNominalHeight, qui est suffisamment grande pour tout contenu de fiche de navigation. Pour que le cadrage de la caméra reste stable, limitez le haut de viewport directement au bord inférieur de turnCard, comme indiqué dans l'exemple d'implémentation.
Composants de prompt dynamiques
Les invites, telles que les alertes d'incident ou les avertissements de radars, se déclenchent indépendamment pendant le guidage actif.
Lorsqu'une invite est prête à être affichée, le SDK appelle le rappel showPrompt(navMap:newPrompt:) de votre délégué et transmet directement une vue newPrompt. Votre délégué est chargé de positionner correctement cette invite sur votre mise en page (généralement ancrée au bord inférieur du conteneur de votre carte).
Étant donné que l'invite entrante couvre la partie inférieure de l'écran, vous devez mettre à jour votre mise en page pour éviter qu'elle ne chevauche la fenêtre d'affichage, le logo Google ou les boutons alignés en bas.
Votre délégué contrôle également la façon dont les invites entrent et sortent de l'écran. Pour en savoir plus, consultez Personnaliser les requêtes.
Gérer la taille de l'écran et le mode grand écran
Pour gérer les modifications des dimensions de la carte et de l'orientation de l'écran, le délégué de mise en page utilise les fonctionnalités suivantes :
Redimensionnement de la vue : ajuste votre mise en page chaque fois que la taille physique de la carte change.
Mode Large : passe à une variante de mise en page au format large lorsque la carte est suffisamment large.
Répondre au redimensionnement de la vue
Les mises en page en écran partagé, les curseurs de conteneur et la rotation de l'appareil déclenchent généralement le rappelsizeChanged(navMap:newState:). Utilisez ce rappel pour effectuer des ajustements responsifs généraux sur votre UI personnalisée. Implémentez sizeChanged(navMap:newState:) pour repositionner les éléments personnalisés pour un nouveau format d'écran, appliquez vos propres points d'arrêt de largeur ou de hauteur personnalisés, et détectez quand un événement de redimensionnement active le mode large pour changer de variante de mise en page en toute sécurité.
Comprendre le mode élargi
Le mode Large s'active lorsque le conteneur de la carte est suffisamment large pour afficher les composants de l'interface utilisateur côte à côte.
Lisez la valeur booléenne wideMode à partir de l'objet d'état pour repositionner vos éléments d'interface utilisateur personnalisés et laisser le centre de la carte dégagé pour le conducteur. Comme les composants fournis par Google, tels que la carte de changement de direction et la carte d'heure d'arrivée prévue, se réduisent et se redimensionnent automatiquement en mode large, la lecture de cette valeur booléenne garantit que votre mise en page s'adapte au moment exact où les composants Google sont mis à jour.
Voici quelques exemples de positionnement des composants de mise en page en mode standard et en mode large :
Mode Portrait standard : placez la carte de direction en haut de l'écran et la carte d'heure d'arrivée prévue en bas.
Mode large : conservez la carte de changement de direction et la carte d'heure d'arrivée prévue le long du bord d'attaque de l'écran, avec la carte de changement de direction en haut et la carte d'heure d'arrivée prévue en bas. Si vous placez l'une ou l'autre de ces cartes loin du bord d'attaque, le comportement sera indéfini. Vous pouvez placer la fenêtre d'affichage et votre propre contenu n'importe où. Un choix courant est le côté de fin des fiches.
Vérification du mode élargi lors des transitions d'état
Si votre mise en page est compatible avec une variante en mode large, évaluez la valeur booléenne wideMode dans chaque rappel de transition d'état, plutôt que de vous fier uniquement à sizeChanged(navMap:newState:).
enableNavigationUI(navMap:newState:) et startGuidance(navMap:newState:oldState:) pour gérer les lancements initiaux de l'application en mode paysage. Cette approche garantit que votre mise en page évalue l'état du mode large et active la variante de mise en page standard ou large appropriée dès qu'un nouvel état de navigation commence.
Alignement sur le style Google
Pour que vos vues personnalisées correspondent à l'espacement des composants fournis par Google, le SDK fournit la classe NavigationStyleValues. Ses propriétés de classe renvoient des mesures en points.
Par exemple, pour placer un bouton personnalisé dans l'angle supérieur de l'écran opposé à la fiche de changement de direction, utilisez NavigationStyleValues.headerTopPadding comme marge supérieure du bouton. Le bouton s'aligne alors sur le bord supérieur de la carte de direction.
Les mesures suivantes sont disponibles :
NavigationStyleValues.buttonHorizontalPaddingNavigationStyleValues.buttonVerticalPaddingNavigationStyleValues.defaultCornerRadiusNavigationStyleValues.elementHorizontalPaddingNavigationStyleValues.headerFooterHorizontalPaddingNavigationStyleValues.headerNominalHeightNavigationStyleValues.headerTopPadding
Les vues fournies par Google incluent également des marges intégrées. Lorsque vous empilez deux vues fournies par Google, limitez l'une d'elles à la layoutMarginsGuide de l'autre afin que leurs marges ne s'additionnent pas. Par exemple, pour placer le widget de vitesse au-dessus de la carte d'heure d'arrivée prévue, limitez l'ancrage inférieur de speedWidget à etaCard.layoutMarginsGuide.topAnchor.
Personnaliser les prompts
Votre délégué de mise en page contrôle la façon dont les invites apparaissent et disparaissent de l'écran, ainsi que les angles arrondis.
Prompts d'animation
Le SDK n'anime pas les invites pour vous. Votre délégué décide de la manière dont chaque invite apparaît et disparaît :
- Dans
showPrompt(navMap:newPrompt:), ajoutez l'invite à la vue de la carte à une position de départ, puis animez-la pour la mettre en place. Par exemple, démarrez l'invite juste en dessous de la zone visible ou derrière une bottom sheet dont le bord supérieur est aligné sur le haut de la bottom sheet. - Dans
hidePrompt(navMap:oldPrompt:), animez l'invite pour qu'elle disparaisse, puis supprimez-la de sa superview une fois l'animation terminée. - Dans
changePrompt(navMap:newPrompt:oldPrompt:), exécutez vos animations de masquage et d'affichage en même temps pour remplacer une invite par une autre.
L'exemple d'implémentation utilise cette approche.
Pour désactiver les invites concernant le trafic, les meilleurs itinéraires et les incidents, définissez la propriété shouldDisplayPrompts de GMSNavigator sur false.
Arrondir les angles des prompts
Les invites sont conformes au protocole NavigationRoundedCornerView. Vous pouvez donc choisir les angles à arrondir. Par défaut, une invite n'arrondit que ses angles supérieurs, ce qui convient à une invite ancrée au bord inférieur de l'écran.
Si vous placez une invite au-dessus d'un autre contenu, comme une bottom sheet, arrondissez les quatre angles :
newPrompt.roundedCorners = [
.layerMinXMinYCorner, .layerMaxXMinYCorner,
.layerMinXMaxYCorner, .layerMaxXMaxYCorner,
]
Exemple : Implémentation d'une mise en page basée sur des contraintes
L'exemple suivant montre un délégué de mise en page de base qui utilise des contraintes de mise en page automatique pour positionner la carte de navigation, la carte d'heure d'arrivée prévue, les boutons, le logo Google et la fenêtre d'affichage. Il fait également glisser les invites sur l'écran et hors de l'écran. Pour que l'exemple reste court, il ne gère pas le mode large, le widget de vitesse ni la barre de progression du trajet. Il convient donc aux applications qui n'acceptent que l'orientation portrait.
import GoogleNavigation
import UIKit
/// A layout delegate that positions the essential navigation UI elements: the
/// turn card, ETA card, buttons, Google logo, and viewport. It also slides
/// prompts on and off the screen.
@MainActor
final class BasicLayoutDelegate: NSObject, NavigationLayoutDelegate {
/// How long prompts take to slide on or off the screen, in seconds.
private static let promptAnimationDuration: TimeInterval = 0.4
/// The views and constraints for one navigation state.
@MainActor
private final class StateElements {
let buttonStack = GMSNavigationHidingStackView(
position: .bottom, alignment: .trailing)
var constraints: [NSLayoutConstraint] = []
private unowned let navMap: GMSMapView
private let buttons: [UIView]
private let topLevelViews: [UIView]
init(
navMap: GMSMapView, buttons: [NavigationUIButton],
topLevelViews: [UIView]
) {
self.navMap = navMap
self.buttons = buttons.map { $0.view }
self.topLevelViews = topLevelViews
}
/// Adds the views to the map view, and then activates the constraints.
func activate() {
topLevelViews.forEach { navMap.addSubview($0) }
buttonStack.arrangedSubviews = buttons
navMap.addSubview(buttonStack)
NSLayoutConstraint.activate(constraints)
}
/// Deactivates the constraints, and then removes the views.
func deactivate() {
NSLayoutConstraint.deactivate(constraints)
buttonStack.removeFromSuperview()
buttonStack.arrangedSubviews = []
topLevelViews.forEach { $0.removeFromSuperview() }
}
}
private var navigationReadyElements: StateElements?
private var activeGuidanceElements: StateElements?
private var currentPrompt: NavigationPromptView?
private var promptBottomConstraint: NSLayoutConstraint?
/// Constraints that apply in both the navigation-ready and active guidance
/// states.
private var sharedConstraints: [NSLayoutConstraint] = []
/// Marks the bottom of the map content area. The guide stays above the ETA
/// card and above any prompt that's on the screen.
private let mapContentsBottomGuide = UILayoutGuide()
// MARK: - Navigation-ready state
func enableNavigationUI(
navMap: GMSMapView, newState: NavigationReadyUIState
) {
let safeArea = navMap.safeAreaLayoutGuide
let bottomGuide = mapContentsBottomGuide
navMap.addLayoutGuide(bottomGuide)
// Keep the guide at the bottom of the safe area unless a higher-priority
// constraint pushes it up.
let lowPriorityBottom = bottomGuide.bottomAnchor.constraint(
equalTo: safeArea.bottomAnchor)
lowPriorityBottom.priority = .defaultLow
sharedConstraints = [
bottomGuide.heightAnchor.constraint(equalToConstant: 0),
lowPriorityBottom,
]
let logo = newState.googleLogo
let viewport = newState.viewport
let elements = StateElements(
navMap: navMap, buttons: newState.buttons,
topLevelViews: [logo, viewport])
let buttonStack = elements.buttonStack
elements.constraints = sharedConstraints + [
buttonStack.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
buttonStack.topAnchor.constraint(equalTo: safeArea.topAnchor),
buttonStack.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
logo.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
logo.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
viewport.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
viewport.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
viewport.topAnchor.constraint(equalTo: safeArea.topAnchor),
viewport.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
]
elements.activate()
navigationReadyElements = elements
}
func disableNavigationUI(
navMap: GMSMapView, oldState: NavigationReadyUIState
) {
currentPrompt?.removeFromSuperview()
currentPrompt = nil
promptBottomConstraint = nil
navigationReadyElements?.deactivate()
navigationReadyElements = nil
navMap.removeLayoutGuide(mapContentsBottomGuide)
}
// MARK: - Active guidance state
func startGuidance(
navMap: GMSMapView, newState: ActiveGuidanceUIState,
oldState: NavigationReadyUIState
) {
navigationReadyElements?.deactivate()
let safeArea = navMap.safeAreaLayoutGuide
let bottomGuide = mapContentsBottomGuide
let turnCard = newState.turnCard
let etaCard = newState.etaCard
let logo = newState.googleLogo
let viewport = newState.viewport
let elements = StateElements(
navMap: navMap, buttons: newState.buttons,
topLevelViews: [turnCard, etaCard, viewport, logo])
let buttonStack = elements.buttonStack
elements.constraints = sharedConstraints + [
turnCard.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
turnCard.topAnchor.constraint(equalTo: navMap.topAnchor),
etaCard.leadingAnchor.constraint(equalTo: navMap.leadingAnchor),
etaCard.bottomAnchor.constraint(equalTo: navMap.bottomAnchor),
bottomGuide.bottomAnchor.constraint(
lessThanOrEqualTo: etaCard.topAnchor),
buttonStack.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
buttonStack.topAnchor.constraint(equalTo: turnCard.bottomAnchor),
buttonStack.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
logo.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
logo.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
// Frame the camera in the open area below the turn card.
viewport.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
viewport.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
viewport.topAnchor.constraint(equalTo: turnCard.bottomAnchor),
viewport.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
]
elements.activate()
activeGuidanceElements = elements
}
func stopGuidance(
navMap: GMSMapView, newState: NavigationReadyUIState,
oldState: ActiveGuidanceUIState
) {
activeGuidanceElements?.deactivate()
activeGuidanceElements = nil
navigationReadyElements?.activate()
}
// MARK: - Prompts
func showPrompt(navMap: GMSMapView, newPrompt: NavigationPromptView) {
navMap.addSubview(newPrompt)
currentPrompt = newPrompt
// Start with the prompt just below the bottom edge of the map view.
let bottomConstraint = newPrompt.bottomAnchor.constraint(
equalTo: navMap.bottomAnchor, constant: newPrompt.bounds.height)
promptBottomConstraint = bottomConstraint
NSLayoutConstraint.activate([
bottomConstraint,
newPrompt.leadingAnchor.constraint(
equalTo: navMap.safeAreaLayoutGuide.leadingAnchor),
// Keep the map content above the prompt.
mapContentsBottomGuide.bottomAnchor.constraint(
lessThanOrEqualTo: newPrompt.topAnchor),
])
// Lay out the prompt in its starting position, and then slide it up.
navMap.layoutIfNeeded()
UIView.animate(withDuration: Self.promptAnimationDuration) {
bottomConstraint.constant = 0
navMap.layoutIfNeeded()
}
}
func hidePrompt(navMap: GMSMapView, oldPrompt: NavigationPromptView) {
guard let bottomConstraint = promptBottomConstraint else { return }
promptBottomConstraint = nil
currentPrompt = nil
// Slide the prompt down, and then remove it.
UIView.animate(withDuration: Self.promptAnimationDuration) {
bottomConstraint.constant = oldPrompt.bounds.height
navMap.layoutIfNeeded()
} completion: { _ in
oldPrompt.removeFromSuperview()
}
}
func changePrompt(
navMap: GMSMapView, newPrompt: NavigationPromptView,
oldPrompt: NavigationPromptView
) {
// Slide the old prompt out and the new prompt in at the same time.
hidePrompt(navMap: navMap, oldPrompt: oldPrompt)
showPrompt(navMap: navMap, newPrompt: newPrompt)
}
}
Pour utiliser le délégué, définissez-le sur votre vue plan avant d'activer la navigation, comme décrit dans la checklist des éléments essentiels.
Limites et problèmes connus
La personnalisation de la mise en page ne s'applique pas à CarPlay, qui utilise des modèles de mise en page fixes. Si vous appelez setLayoutDelegate(_:) sur une vue de carte que vous créez pour un écran CarPlay, la méthode génère une erreur.
Pour les problèmes qui affectent des versions spécifiques du SDK, consultez la section Problèmes connus dans les notes de version.