
Usa la personalización del diseño para controlar la jerarquía de vistas y la ubicación en la pantalla de las vistas proporcionadas por Google y tus propias vistas empresariales personalizadas.
En lugar de agregar vistas personalizadas en espacios de diseño predefinidos, implementa un delegado de diseño para administrar el diseño de toda la pantalla. Durante las transiciones de estado de navegación, tu delegado de diseño recibe los componentes proporcionados por Google, como la tarjeta de giro, la tarjeta de hora de llegada estimada y los botones. Coloca estos elementos junto con tu propio contenido personalizado usando las restricciones estándar de Auto Layout de iOS.
Usa este framework para posicionar información personalizada de la empresa, como el estado del pedido o las instrucciones de retiro, de forma precisa en la pantalla y, al mismo tiempo, evitar que se superpongan tus vistas.
Cómo funciona la personalización del diseño
La personalización del diseño usa un patrón de diseño de delegación. En lugar de dibujar o posicionar automáticamente los componentes de la IU en la pantalla, el SDK los pasa directamente a una clase personalizada que escribes: el delegado de diseño.
Para personalizar tu diseño, crea una clase que cumpla con el protocoloNavigationLayoutDelegate y pasa una instancia al método setLayoutDelegate(_:) de tu GMSMapView. Cada vez que cambia el estado de navegación (por ejemplo, cuando se pasa del mapa base a la guía paso a paso activa), el SDK ejecuta los métodos de devolución de llamada en tu delegado y proporciona los componentes de la IU disponibles para ese estado específico.
Para proporcionar una experiencia del usuario integrada y fluida, este framework establece una separación clara de responsabilidades entre tu app y el SDK. Usa el delegado de diseño para personalizar lo siguiente:
- Compila la jerarquía de vistas: Elige exactamente qué componentes de Google y vistas empresariales personalizadas agregar a la pantalla para cada estado de la IU.
- Posiciona cada elemento: Establece anclajes, márgenes y ubicaciones de diseño precisos en la pantalla. Evita aplicar restricciones de ancho o altura personalizadas a los componentes de Google, ya que estos calculan sus propias dimensiones internas.
- Enmarcar el mapa base: Define los límites visibles de la cámara del mapa con las coordenadas del componente del viewport.
- Crea capas en la pantalla: Decide si las vistas personalizadas flotan por encima, por debajo o junto a los controles integrados de Google.
Mientras tanto, no puedes personalizar los siguientes componentes de Google con el delegado de diseño:
- Dimensiones de los componentes: Es el tamaño y las dimensiones internas de los componentes proporcionados por Google, que el SDK calcula automáticamente.
- Condiciones de activación: Son las condiciones en las que aparecen alertas o mensajes dinámicos según los datos de la ruta en tiempo real.
Principios de implementación
Cuando escribas un delegado de diseño, ten en cuenta las siguientes reglas para evitar errores de diseño o fallas en el tiempo de ejecución:
- La opción predeterminada es una pantalla vacía: Los componentes que proporciona Google solo aparecen si tu delegado los agrega de forma explícita a la jerarquía de vistas y los posiciona.
- No se admiten las APIs heredadas: No se admitirán las propiedades y los métodos que controlan el modelo de diseño basado en ranuras heredado, y es posible que no se comporten como se espera cuando un delegado personalizado esté activo.
- No modifiques las estructuras de vistas internas: No recorras ni modifiques las jerarquías de vistas de los componentes proporcionados por Google (como la tarjeta de giro o la tarjeta de hora de llegada estimada). Debido a que estas jerarquías de vistas internas son detalles de implementación subyacentes, están sujetas a cambios en las versiones del SDK. Si los modificas, es posible que el diseño se rompa durante las futuras actualizaciones del SDK.
Compatibilidad con la API heredada
Para obtener un comportamiento de diseño confiable cuando uses un delegado de diseño personalizado, evita usar las siguientes APIs heredadas basadas en ranuras, que se darán de baja. Para migrar una app existente, reemplaza el uso de estas APIs por código en tu delegado de diseño personalizado:
Mostrar las APIs heredadas basadas en espacios
| API heredada | Reemplazo de LayoutDelegate |
|---|---|
isNavigationHeaderEnabled |
Lee la vista turnCard en
startGuidance(navMap:newState:oldState:). |
isNavigationFooterEnabled |
Lee la vista etaCard en
startGuidance(navMap:newState:oldState:). |
isNavigationTripProgressBarEnabled |
Lee la vista tripProgressBar en
startGuidance(navMap:newState:oldState:). |
isNavigationReportIncidentButtonEnabled |
Ubica el botón .reporting en el array buttons. |
setHeaderAccessoryView(_:) y
invalidateLayout(forAccessoryView:) |
Agrega tu propia vista al diseño y fíjalo a la turnCard. |
padding y paddingAdjustmentBehavior |
Posiciona la vista viewport para encuadrar la cámara. |
navigationHeaderLayoutGuide |
Ancla tus vistas personalizadas directamente al turnCard. |
navigationFooterLayoutGuide |
Ancla tus vistas personalizadas directamente al etaCard. |
bottomTrailingButtonsLayoutGuide |
Ancla los botones directamente a las guías de etaCard o del contenedor. |
bottomTrailingButtonsAlternateLayoutGuide |
Ancla tus botones directamente al botón .reporting o a las restricciones de wideMode. |
navigatorWillPresentPrompt(_:) y
navigatorDidDismissPrompt(_:) |
Confía en tus devoluciones de llamada de instrucciones de NavigationLayoutDelegate, como showPrompt(navMap:newPrompt:). |
Mientras se establece un delegado de diseño personalizado, las guías de diseño heredadas (como navigationHeaderLayoutGuide o bottomTrailingButtonsLayoutGuide) y los métodos de vista de accesorios del encabezado no tienen efecto. En su lugar, fija tus vistas a las que proporciona Google.
La propiedad compassButton de GMSUISettings sigue controlando la brújula mientras la IU de navegación está inhabilitada. Mientras la navegación está habilitada, tu delegado decide si mostrar la brújula del array buttons.
Lista de tareas de Essentials
Sigue estos pasos y requisitos esenciales para implementar correctamente tu delegado de diseño:
-
Establece el delegado antes de habilitar la navegación: Llama a
setLayoutDelegate(_:)en tuGMSMapViewantes de habilitar la navegación conisNavigationEnabledoenableNavigation(with:). El método arroja un error si la navegación ya está habilitada o si creaste la vista de mapa para una pantalla de CarPlay. La vista de mapa mantiene una referencia sólida a tu delegado. Para restablecer el diseño integrado, pasanilmientras la navegación está inhabilitada.// 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
-
Adjunta vistas obligatorias: Debes agregar el logotipo de Google (
googleLogo) y el viewport (viewport) a tu contenedor de diseño en todos los estados de navegación. -
Respeta las dimensiones fijas: El SDK determina las dimensiones de varios componentes proporcionados por Google. No apliques restricciones de ancho o altura personalizados a los siguientes elementos:
- Tarjeta de pasos
- tarjeta de hora estimada de llegada
- Instrucciones entrantes
- Logotipo de Google
- Widget de velocidad
-
Agrega elementos como vistas secundarias: Agrega las vistas proporcionadas por Google directamente como vistas secundarias a tu vista de contenedor personalizada o a la
GMSMapViewen sí. -
Alinea con guías de diseño: Las vistas que proporciona Google incluyen márgenes visuales integrados. Para alinear perfectamente tus elementos personalizados con los componentes que proporciona Google, restringe tus vistas a la propiedad
layoutMarginsGuidede la vista de Google. -
Realiza una limpieza cuando se salga del estado: En
stopGuidance(navMap:newState:oldState:), quita las vistas de orientación activas y desactiva sus restricciones antes de restablecer el diseño listo para la navegación. EndisableNavigationUI(navMap:oldState:), quita todas las vistas y guías de diseño que agregaste, incluido cualquier mensaje en la pantalla. Esto evita que las vistas de un estado se superpongan con el siguiente. -
Compatibilidad con accesibilidad y escritura de derecha a izquierda: Las vistas que proporciona Google cumplen con los estándares de accesibilidad, pero tu delegado es responsable de la accesibilidad del diseño general. Para admitir idiomas de derecha a izquierda, usa las anclas iniciales y finales en lugar de las izquierdas y derechas.
-
Evita las APIs de diseño heredadas: No uses propiedades de modificación de diseño heredadas en
GMSUISettingsni guías de diseño heredadas enGMSMapViewmientras un delegado personalizado esté activo. Es posible que el SDK ignore estas llamadas heredadas cuando se adjunte un delegado personalizado.
Estados de la IU y componentes de Google
Cuando se produce una transición de estado de navegación, el SDK pasa un objeto NavigationUIState de solo lectura a la devolución de llamada del delegado. Este objeto agrupa las marcas de configuración de diseño actuales con los componentes de Google (por ejemplo, instancias de UIView) que necesitas para renderizar la pantalla.
El delegado de diseño administra la jerarquía de vistas en cuatro estados operativos. En el siguiente diagrama de estado, se ilustra cómo el SDK realiza la transición entre los estados de navegación y qué métodos de devolución de llamada ejecuta en tu delegado:
Los componentes específicos de Google que se proporcionan a tu delegado dependen de la fase actual del ciclo de vida de la navegación.
Componentes obligatorios (todos los estados de navegación)
Independientemente de la fase de navegación actual, debes incluir, posicionar y mantener visibles los siguientes componentes de Google en tu jerarquía de vistas:
Logotipo de Google (
googleLogo): Este componente muestra el logotipo obligatorio de Google Maps. Si el botón Volver a centrar está habilitado, el logotipo se transforma automáticamente en este botón cuando el conductor desplaza el mapa para alejarlo de su vehículo. Como resultado, la ubicación del logotipo determina dónde aparece el botón Volver a centrar. Se recomienda colocar el logotipo en la esquina inferior izquierda de tu diseño para que coincida con las expectativas estándar de la IU de mapas.Viewport (
viewport): Es un objetoUIViewinvisible que define los límites de encuadre de la cámara. La ubicación del viewport determina exactamente dónde el SDK centra el ícono de contenido de formato de cheurón del vehículo y dibuja la línea de ruta activa. Posiciona el viewport de modo que cubra el área abierta y sin obstrucciones de la pantalla, evitando de forma segura las superposiciones opacas, como las hojas inferiores personalizadas.
Componentes listos para la navegación
Durante el estado de navegación lista, la IU se mantiene al mínimo para que el enfoque permanezca en el mapa base. Tu objeto NavigationReadyUIState proporciona acceso a lo siguiente:
- buttons: Es un array de vistas de acciones flotantes configuradas para el mapa base (por lo general, solo el botón de la brújula). Debido a que Google podría agregar o reordenar botones en futuras versiones del SDK, evita depender de índices de arrays fijos. En cambio, itera el array, lee el tipo de cada botón comparando su propiedad
typeconNavigationButtonKnownType.compassy extrae la vista física llamando a su propiedadview. Ten en cuenta que los botones exactos de este array difieren de los que están disponibles durante la guía activa.
Componentes de orientación activa
Cuando comienza la guía paso a paso, el SDK desbloquea el conjunto completo de controles de navegación. Tu objeto ActiveGuidanceUIState proporciona acceso a los siguientes componentes de Google:
turnCard: Es el banner del encabezado principal que muestra las próximas instrucciones de maniobra, las mediciones de distancia y la guía de carriles. Colócala en la parte superior de tu diseño para establecer una jerarquía de navegación familiar y fijar tus vistas personalizadas alrededor de ella.etaCard: Es el banner de pie de página que muestra la hora de llegada estimada, el tiempo de viaje restante y la distancia restante hasta el destino. Colócalo en el borde inferior de la pantalla o integra sus coordenadas con tus hojas de administración de tareas personalizadas.tripProgressBar: Es una barra de progreso vertical que muestra al conductor la distancia que recorrió en la ruta actual. A diferencia de los diseños heredados que fijaban este elemento estrictamente al borde del mapa, tienes total libertad para fijarlo en cualquier lugar, por ejemplo, a lo largo de un borde lateral de tu contenedor personalizado.speedWidget: Es un control flotante para el velocímetro y los límites de velocidad publicados. Según la configuración de tu API (las propiedadesshouldDisplaySpeedometeryshouldDisplaySpeedLimitdeGMSMapView) y la disponibilidad de los datos, esta vista cambia de tamaño de forma dinámica entre cuatro estados visuales durante el tiempo de ejecución (no muestra nada, solo la velocidad actual, solo el límite de velocidad o ambas lecturas). Debido a que el widget puede cambiar entre estos tamaños sin previo aviso, siempre fija las vistas circundantes con restricciones para que tu diseño se adapte automáticamente y evite superposiciones espaciales.
buttons: Es un array expandido de vistas de acciones flotantes para el estado de guía activo (por lo general, incluye la brújula y el botón de aviso de incidentes). Al igual que en el estado listo para la navegación, puedes ubicar y extraer vistas de botones individuales filtrando el array portype(.compasso.reporting) y extrayendo la vista física con su propiedadview. Luego, puedes posicionarlos de forma independiente o agregarlos a unGMSNavigationHidingStackView. Esta vista de pila organiza los botones de forma vertical y, cuando se agota el espacio, los oculta a partir del final de su arrayarrangedSubviews. No configuresisHiddenen las vistas que administra.
NavigationStyleValues.headerNominalHeight, que es lo suficientemente alta para cualquier contenido de la tarjeta de giro. Para mantener estable el encuadre de la cámara, restringe la parte superior de viewport directamente al borde inferior de turnCard, como se muestra en la implementación de ejemplo.
Componentes de instrucciones dinámicas
Los mensajes, como las alertas de incidentes o las advertencias de cámaras de seguridad, se activan de forma independiente durante la navegación activa.
Cuando un mensaje está listo para mostrarse, el SDK llama a la devolución de llamada showPrompt(navMap:newPrompt:) de tu delegado y pasa una vista newPrompt directamente. Tu delegado es responsable de posicionar sin problemas este mensaje sobre tu diseño (por lo general, anclado al borde inferior del contenedor del mapa).
Debido a que el mensaje entrante cubre la parte inferior de la pantalla, debes actualizar tu diseño para evitar que el mensaje se superponga con el viewport, el logotipo de Google o cualquier botón alineado en la parte inferior.
Tu delegado también controla cómo entran y salen los mensajes de la pantalla. Para obtener más detalles, consulta Personaliza instrucciones.
Cómo controlar el tamaño de la pantalla y el modo ancho
Para controlar los cambios en las dimensiones del mapa y la orientación de la pantalla, el delegado de diseño usa las siguientes funciones:
Cambio de tamaño de la vista: Ajusta tu diseño cada vez que cambia el tamaño físico del mapa.
Modo ancho: Cambia a una variante de diseño de formato ancho cuando el mapa es lo suficientemente ancho.
Cómo responder al cambio de tamaño de la vista
Los diseños de pantalla dividida, los controles deslizantes de contenedores y la rotación del dispositivo suelen activar la devolución de llamadasizeChanged(navMap:newState:). Usa esta devolución de llamada para realizar ajustes responsivos generales en tu IU personalizada. Implementa sizeChanged(navMap:newState:) para cambiar la posición de los elementos personalizados para una nueva relación de aspecto de la pantalla, aplica tus propios puntos de interrupción de ancho o altura personalizados, y detecta cuándo un evento de cambio de tamaño activa el modo ancho para intercambiar de forma segura las variantes de diseño.
Información sobre el modo ancho
El modo ancho se activa cuando el contenedor del mapa es lo suficientemente amplio como para mostrar los componentes de la IU uno al lado del otro.
Lee el valor booleano wideMode del objeto de estado para cambiar la posición de tus elementos de la IU personalizados y mantener el centro del mapa despejado para el conductor. Debido a que los componentes proporcionados por Google, como la tarjeta de giro y la tarjeta de hora de llegada estimada, se reducen y cambian de forma automáticamente en el modo ancho, leer este valor booleano garantiza que tu diseño se adapte en el mismo momento en que se actualizan los componentes de Google.
Considera los siguientes ejemplos para posicionar los componentes de tu diseño en los modos estándar y ancho:
Modo Retrato estándar: Coloca la tarjeta de giro en la parte superior de la pantalla y la tarjeta de hora de llegada en la parte inferior.
Modo ancho: Mantén la tarjeta de giro y la tarjeta de hora de llegada estimada en el borde principal de la pantalla, con la tarjeta de giro en la parte superior y la tarjeta de hora de llegada estimada en la parte inferior. Si colocas cualquiera de las tarjetas lejos del borde inicial, se producirá un comportamiento indefinido. Puedes colocar el viewport y tu propio contenido en cualquier lugar; una opción común es el lado final de las tarjetas.
Cómo verificar el modo ancho durante las transiciones de estado
Si tu diseño admite una variación de modo amplio, evalúa el booleano wideMode dentro de cada devolución de llamada de transición de estado, en lugar de depender solo de sizeChanged(navMap:newState:).
enableNavigationUI(navMap:newState:) y startGuidance(navMap:newState:oldState:), para controlar los inicios de la app en orientación horizontal. Este enfoque garantiza que tu diseño evalúe el estado del modo ancho y active la variante de diseño estándar o ancho correcta en el momento en que comienza un nuevo estado de navegación.
Alineación con el estilo de Google
Para ayudar a que tus vistas personalizadas coincidan con el espaciado de los componentes que proporciona Google, el SDK proporciona la clase NavigationStyleValues. Las propiedades de su clase devuelven
mediciones en puntos.
Por ejemplo, para colocar un botón personalizado en la esquina superior de la pantalla opuesta a la tarjeta de giro, usa NavigationStyleValues.headerTopPadding como margen superior del botón. Luego, el botón se alinea con el borde superior de la tarjeta de giro.
Están disponibles las siguientes mediciones:
NavigationStyleValues.buttonHorizontalPaddingNavigationStyleValues.buttonVerticalPaddingNavigationStyleValues.defaultCornerRadiusNavigationStyleValues.elementHorizontalPaddingNavigationStyleValues.headerFooterHorizontalPaddingNavigationStyleValues.headerNominalHeightNavigationStyleValues.headerTopPadding
Las vistas proporcionadas por Google también incluyen márgenes integrados. Cuando apiles dos vistas proporcionadas por Google, restringe una a la layoutMarginsGuide de la otra para que sus márgenes no se sumen. Por ejemplo, para colocar el widget de velocidad sobre la tarjeta de hora de llegada estimada, restringe el anclaje inferior de speedWidget a etaCard.layoutMarginsGuide.topAnchor.
Personaliza las instrucciones
El delegado de diseño controla cómo entran y salen los mensajes de la pantalla, y cuáles de sus esquinas se redondean.
Instrucciones de animación
El SDK no anima las instrucciones por ti. Tu delegado decide cómo aparece y desaparece cada mensaje:
- En
showPrompt(navMap:newPrompt:), agrega el mensaje a la vista de mapa en una posición inicial y, luego, anímalo para que se mueva a su lugar. Por ejemplo, inicia el mensaje justo debajo del área visible o detrás de una hoja inferior con el borde superior alineado con la parte superior de la hoja. - En
hidePrompt(navMap:oldPrompt:), anima el mensaje para que desaparezca de la vista y, luego, quítalo de su vista superior cuando finalice la animación. - En
changePrompt(navMap:newPrompt:oldPrompt:), ejecuta las animaciones de ocultar y mostrar al mismo tiempo para reemplazar un mensaje por otro.
La implementación de ejemplo usa este enfoque.
Para desactivar las indicaciones de tráfico, mejores rutas y accidentes, establece la propiedad shouldDisplayPrompts de GMSNavigator en false.
Redondeo de las esquinas de las instrucciones
Las indicaciones se ajustan al protocolo NavigationRoundedCornerView, por lo que puedes elegir cuáles de sus esquinas se redondean. De forma predeterminada, un mensaje solo redondea sus esquinas superiores, lo que es adecuado para un mensaje que está anclado al borde inferior de la pantalla.
Si colocas un mensaje sobre otro contenido, como una hoja inferior, redondea las cuatro esquinas:
newPrompt.roundedCorners = [
.layerMinXMinYCorner, .layerMaxXMinYCorner,
.layerMinXMaxYCorner, .layerMaxXMaxYCorner,
]
Ejemplo: Implementación de un diseño basado en restricciones
En el siguiente ejemplo, se muestra un delegado de diseño básico que usa restricciones de diseño automático para posicionar la tarjeta de giro, la tarjeta de hora de llegada estimada, los botones, el logotipo de Google y el viewport. También desliza las instrucciones para que aparezcan y desaparezcan de la pantalla. Para que el ejemplo sea breve, no se controla el modo ancho, el widget de velocidad ni la barra de progreso del viaje, por lo que es adecuado para apps que solo admiten la orientación vertical.
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)
}
}
Para usar el delegado, configúralo en tu vista de mapa antes de habilitar la navegación, como se describe en la lista de tareas esenciales.
Limitaciones y problemas conocidos
La personalización del diseño no se aplica a CarPlay, que usa plantillas de diseño fijas. Si llamas a setLayoutDelegate(_:) en una vista de mapa que creas para una pantalla de CarPlay, el método arroja un error.
Para conocer los problemas que afectan a versiones específicas del SDK, consulta Problemas conocidos en las notas de la versión.