
Dzięki dostosowywaniu układu możesz kontrolować hierarchię widoków i umieszczanie na ekranie zarówno widoków udostępnianych przez Google, jak i własnych widoków biznesowych.
Zamiast dodawać widoki niestandardowe do wstępnie zdefiniowanych boksów układu, zaimplementuj delegata układu, który będzie zarządzać układem całego ekranu. Podczas zmian stanu nawigacji Twój delegat układu otrzymuje komponenty dostarczone przez Google, takie jak karta skrętu, karta szacowanego czasu dotarcia na miejsce i przyciski. Umieść te elementy obok własnych treści niestandardowych, korzystając ze standardowych ograniczeń automatycznego układu iOS.
Użyj tej platformy, aby precyzyjnie umieścić na ekranie niestandardowe informacje o firmie, takie jak stan zamówienia lub instrukcje odbioru, i zapobiec nakładaniu się widoków.
Jak działa dostosowywanie układu
Dostosowywanie układu korzysta ze wzorca projektowego przekazywania. Zamiast automatycznie rysować lub pozycjonować komponenty interfejsu na ekranie, pakiet SDK przekazuje je bezpośrednio do niestandardowej klasy, którą piszesz: delegata układu.
Aby dostosować układ, utwórz klasę zgodną z protokołemNavigationLayoutDelegate i przekaż instancję do metody setLayoutDelegate(_:) obiektu GMSMapView. Za każdym razem, gdy zmienia się stan nawigacji, np. gdy przechodzisz z mapy podstawowej do aktywnych szczegółowych wskazówek dojazdu, pakiet SDK wykonuje metody wywołania zwrotnego w obiekcie delegowanym i udostępnia komponenty interfejsu dostępne w danym stanie.
Aby zapewnić użytkownikom płynne i zintegrowane działanie, ta platforma wyraźnie rozdziela odpowiedzialność między aplikację a pakiet SDK. Użyj delegata układu, aby dostosować te elementy:
- Utworzyć hierarchię widoków: wybierz dokładnie, które komponenty Google i niestandardowe widoki biznesowe chcesz dodać do ekranu w przypadku każdego stanu interfejsu.
- Pozycjonowanie każdego elementu: ustawianie precyzyjnych punktów zakotwiczenia, marginesów i miejsca w układzie. Unikaj stosowania niestandardowych ograniczeń szerokości lub wysokości do komponentów Google, ponieważ komponenty Google obliczają własne wymiary wewnętrzne.
- Kadrowanie mapy podstawowej: określ widoczne granice kamery mapy za pomocą współrzędnych komponentu widocznego obszaru.
- Warstwy ekranu: zdecyduj, czy widoki niestandardowe mają być wyświetlane nad, pod czy obok wbudowanych elementów sterujących Google.
Nie możesz jednak dostosować za pomocą delegata układu tych komponentów Google:
- Wymiary komponentu: rozmiar i wymiary wewnętrzne komponentów dostarczanych przez Google, które pakiet SDK oblicza automatycznie.
- Warunki aktywacji: kiedy dynamiczne alerty lub prośby pojawiają się na podstawie danych o trasie w czasie rzeczywistym.
Zasady implementacji
Podczas pisania delegata układu pamiętaj o tych regułach, aby zapobiec błędom układu lub awariom w czasie działania:
- Domyślnie jest to pusty ekran: komponenty dostarczane przez Google pojawiają się tylko wtedy, gdy delegat wyraźnie doda je do hierarchii widoków i ustawi ich pozycję.
- Starsze interfejsy API nie są obsługiwane: właściwości i metody, które kontrolują starszy model układu opartego na boksach, nie będą obsługiwane i mogą nie działać zgodnie z oczekiwaniami, gdy aktywny jest niestandardowy obiekt delegowany.
- Nie modyfikuj wewnętrznych struktur widoku: nie przechodź przez hierarchie widoków komponentów dostarczanych przez Google (takich jak karta skrętu lub karta szacowanego czasu dotarcia na miejsce) ani ich nie modyfikuj. Te wewnętrzne hierarchie widoków są szczegółami implementacji, więc mogą się zmieniać w poszczególnych wersjach pakietu SDK. Ich zmodyfikowanie może spowodować uszkodzenie układu podczas przyszłych aktualizacji pakietu SDK.
Zgodność ze starszą wersją interfejsu API
Aby zapewnić niezawodne działanie układu podczas korzystania z niestandardowego delegata układu, unikaj używania tych starszych interfejsów API opartych na boksach, które mają zostać wycofane. Aby przenieść istniejącą aplikację, zastąp użycie tych interfejsów API kodem w niestandardowym delegacie układu:
Pokaż starsze interfejsy API oparte na boksach
| Starszy interfejs API | Zastąpienie delegata układu |
|---|---|
isNavigationHeaderEnabled |
Odczytaj widok turnCard w startGuidance(navMap:newState:oldState:). |
isNavigationFooterEnabled |
Odczytaj widok etaCard w startGuidance(navMap:newState:oldState:). |
isNavigationTripProgressBarEnabled |
Odczytaj widok tripProgressBar w startGuidance(navMap:newState:oldState:). |
isNavigationReportIncidentButtonEnabled |
Znajdź przycisk .reporting w tablicy buttons. |
setHeaderAccessoryView(_:) i invalidateLayout(forAccessoryView:) |
Dodaj do układu własny widok i zakotwicz go w turnCard. |
padding i paddingAdjustmentBehavior |
Ustaw widok viewport, aby wykadrować kamerę. |
navigationHeaderLayoutGuide |
Przypnij widoki niestandardowe bezpośrednio do turnCard. |
navigationFooterLayoutGuide |
Przypnij widoki niestandardowe bezpośrednio do etaCard. |
bottomTrailingButtonsLayoutGuide |
Zakotwicz przyciski bezpośrednio w etaCard lub prowadnicach kontenera. |
bottomTrailingButtonsAlternateLayoutGuide |
Zakotwicz przyciski bezpośrednio w ograniczeniach przycisku .reporting lub wideMode. |
navigatorWillPresentPrompt(_:) i navigatorDidDismissPrompt(_:) |
Polegaj na wywołaniach zwrotnych promptów NavigationLayoutDelegate, takich jak showPrompt(navMap:newPrompt:). |
Gdy ustawiony jest niestandardowy delegat układu, starsze prowadnice układu (takie jak navigationHeaderLayoutGuide lub bottomTrailingButtonsLayoutGuide) i metody widoku akcesoriów nagłówka nie mają żadnego efektu. Zamiast tego zakotwicz widoki w widokach udostępnianych przez Google.
Właściwość compassButton obiektu GMSUISettings nadal kontroluje kompas, gdy interfejs nawigacji jest wyłączony. Gdy nawigacja jest włączona, Twój obiekt delegowany decyduje, czy wyświetlać kompas z tablicy buttons.
Lista kontrolna Essentials
Aby prawidłowo wdrożyć delegata układu, wykonaj te podstawowe czynności i spełnij te wymagania:
-
Ustaw przedstawiciela przed włączeniem nawigacji: wywołaj
setLayoutDelegate(_:)wGMSMapView, zanim włączysz nawigację za pomocąisNavigationEnabledlubenableNavigation(with:). Metoda zgłasza błąd, jeśli nawigacja jest już włączona lub jeśli widok mapy został utworzony na ekranie CarPlay. Widok mapy zachowuje silne odwołanie do Twojego obiektu delegowanego. Aby przywrócić wbudowany układ, przekażnil, gdy nawigacja jest wyłączona.// 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
-
Dołącz obowiązkowe widoki: we wszystkich stanach nawigacji musisz dodać do kontenera układu logo Google (
googleLogo) i obszar wyświetlania (viewport). -
Uwzględnianie stałych wymiarów: pakiet SDK określa wymiary kilku komponentów dostarczanych przez Google. Nie stosuj niestandardowych ograniczeń szerokości ani wysokości do tych elementów:
- Karta nawigacji
- Karta szacowanego czasu dotarcia na miejsce
- Przychodzące prompty
- Logo Google
- Widżet prędkości
-
Dodaj elementy jako widoki podrzędne: dodaj widoki udostępnione przez Google bezpośrednio jako widoki podrzędne do niestandardowego widoku kontenera lub do samego elementu
GMSMapView. -
Wyrównywanie za pomocą prowadnic układu: widoki udostępniane przez Google zawierają wbudowane marginesy wizualne. Aby idealnie dopasować elementy niestandardowe do komponentów dostarczanych przez Google, ogranicz widoki do właściwości
layoutMarginsGuidewidoku Google. -
Czyszczenie przy wyjściu ze stanu: w metodzie
stopGuidance(navMap:newState:oldState:)usuń aktywne widoki wskazówek i dezaktywuj ich ograniczenia, zanim przywrócisz układ gotowy do nawigacji. W plikudisableNavigationUI(navMap:oldState:)usuń wszystkie dodane widoki i prowadnice układu, w tym wszelkie instrukcje na ekranie. Zapobiega to nakładaniu się widoków z jednego stanu na następny. -
Własne ułatwienia dostępu i obsługa języków pisanych od prawej do lewej: widoki udostępniane przez Google spełniają standardy ułatwień dostępu, ale za ułatwienia dostępu w całym układzie odpowiada delegat. Aby obsługiwać języki pisane od prawej do lewej, używaj kotwic początkowych i końcowych zamiast lewych i prawych.
-
Unikaj starszych interfejsów API układu: nie używaj starszych właściwości modyfikujących układ w
GMSUISettingsani starszych przewodników po układzie wGMSMapView, gdy aktywny jest niestandardowy delegat. Gdy dołączony jest niestandardowy obiekt delegowany, pakiet SDK może ignorować te połączenia starszego typu.
Stany interfejsu i komponenty Google
Gdy następuje zmiana stanu nawigacji, pakiet SDK przekazuje do wywołania zwrotnego obiektu delegowanego obiekt NavigationUIState tylko do odczytu. Ten obiekt łączy bieżące flagi konfiguracji układu z komponentami Google (np. instancjami UIView), które są potrzebne do renderowania ekranu.
Delegat układu zarządza hierarchią widoków w 4 stanach operacyjnych. Poniższy diagram stanów ilustruje, jak pakiet SDK przechodzi między stanami nawigacji i jakie metody wywołania zwrotnego wykonuje na delegacie:
Konkretne komponenty Google udostępniane obiektowi delegowanemu zależą od bieżącego etapu cyklu życia nawigacji.
Wymagane komponenty (wszystkie stany nawigacji)
Niezależnie od bieżącego etapu nawigacji musisz uwzględnić, umieścić i utrzymać widoczność tych komponentów Google w hierarchii widoków:
Logo Google (
googleLogo): ten komponent wyświetla obowiązkowe logo Map Google. Jeśli przycisk Wyśrodkuj jest włączony, logo automatycznie przekształca się w ten przycisk, gdy kierowca przewija mapę poza pojazd. W związku z tym umiejscowienie logo określa, gdzie pojawi się przycisk Wyśrodkuj. Zalecamy umieszczenie logo w lewym dolnym rogu układu, aby odpowiadało standardowym oczekiwaniom dotyczącym interfejsu mapy.Widoczny obszar (
viewport): niewidoczny elementUIView, który określa granice kadrowania kamery. Umiejscowienie widocznego obszaru określa, gdzie pakiet SDK wyśrodkuje ikonę pojazdu i narysuje aktywną linię trasy. Ustaw widoczny obszar tak, aby obejmował otwarty, niezasłonięty obszar ekranu, bezpiecznie unikając nieprzezroczystych nakładek, takich jak niestandardowe plansze dolne.
komponenty gotowe do nawigacji
W stanie gotowości do nawigacji interfejs pozostaje minimalistyczny, aby skupić uwagę na mapie podstawowej. Obiekt NavigationReadyUIState zapewnia dostęp do tych funkcji:
- buttons: tablica pływających widoków działań skonfigurowanych dla mapy podstawowej (zwykle tylko przycisk kompasu). Google może dodawać przyciski lub zmieniać ich kolejność w przyszłych wersjach pakietu SDK, dlatego unikaj polegania na stałych indeksach tablicy. Zamiast tego przejdź przez tablicę, odczytaj typ każdego przycisku, porównując jego właściwość
typezNavigationButtonKnownType.compass, i wyodrębnij widok fizyczny, wywołując jego właściwośćview. Pamiętaj, że przyciski w tym zestawie różnią się od tych dostępnych podczas aktywnego prowadzenia.
aktywne komponenty wskazówek
Gdy rozpocznie się szczegółowa nawigacja, pakiet SDK odblokuje pełny zestaw elementów sterujących nawigacją. Obiekt ActiveGuidanceUIState zapewnia dostęp do tych komponentów Google:
turnCard: główny baner nagłówka, który wyświetla wskazówki dotyczące nadchodzącego manewru, odległość i wskazówki dotyczące pasa ruchu. Umieść go u góry układu, aby utworzyć znajomą hierarchię nawigacji i zakotwiczyć wokół niego widoki niestandardowe.etaCard: baner w stopce wyświetlający szacowany czas przybycia, pozostały czas podróży i pozostałą odległość do miejsca docelowego. Umieść go wzdłuż dolnej krawędzi ekranu lub zintegruj jego współrzędne z niestandardowymi arkuszami zarządzania zadaniami.tripProgressBar: pionowy pasek postępu, który pokazuje kierowcy, jaką część trasy już pokonał. W przeciwieństwie do starszych układów, które ściśle wiązały ten element z krawędzią mapy, masz pełną swobodę zakotwiczenia go w dowolnym miejscu – na przykład wzdłuż bocznej krawędzi niestandardowego kontenera.speedWidget: pływający element sterujący prędkościomierzem i opublikowanymi ograniczeniami prędkości. W zależności od ustawień interfejsu API (właściwościshouldDisplaySpeedometerishouldDisplaySpeedLimitobiektuGMSMapView) oraz dostępności danych ten widok dynamicznie zmienia rozmiar między 4 stanami wizualnymi w czasie działania (nie wyświetla niczego, tylko bieżącą prędkość, tylko ograniczenie prędkości lub oba odczyty). Ponieważ widżet może zmieniać rozmiar bez powiadomienia, zawsze zakotwiczaj otaczające widoki za pomocą ograniczeń, aby układ automatycznie dostosowywał się do zmian i nie dochodziło do nakładania się elementów.
buttons: rozszerzona tablica pływających widoków działań dla aktywnego stanu wskazówek (zwykle obejmuje zarówno kompas, jak i przycisk zgłaszania zdarzeń). Podobnie jak w stanie gotowości do nawigacji, możesz zlokalizować i wyodrębnić poszczególne widoki przycisków, filtrując tablicę wedługtype(.compasslub.reporting) i wyodrębniając widok fizyczny za pomocą właściwościview. Możesz je następnie umieścić niezależnie lub dodać doGMSNavigationHidingStackView. Ten widok stosu układa przyciski pionowo, a gdy zabraknie miejsca, ukrywa je, zaczynając od końca tablicyarrangedSubviews. Nie ustawiaj właściwościisHiddenw widokach, którymi zarządza.
NavigationStyleValues.headerNominalHeight, która jest wystarczająca
dla każdej treści karty skrętu. Aby zachować stabilne kadrowanie aparatu, ogranicz górną krawędź elementu viewport bezpośrednio do dolnej krawędzi elementu turnCard, jak pokazano w przykładowej implementacji.
dynamiczne komponenty promptu
Podpowiedzi, takie jak alerty o incydentach lub ostrzeżenia o fotoradarach, są uruchamiane niezależnie podczas aktywnego prowadzenia.
Gdy prośba jest gotowa do wyświetlenia, pakiet SDK wywołuje wywołanie zwrotne showPrompt(navMap:newPrompt:) i przekazuje widok newPrompt bezpośrednio. Delegat odpowiada za płynne pozycjonowanie tej prośby nad układem (zwykle jest ona zakotwiczona na dolnej krawędzi kontenera mapy).
Ponieważ przychodzący komunikat zakrywa dolną część ekranu, musisz zaktualizować układ, aby komunikat nie nakładał się na widoczny obszar, logo Google ani żadne przyciski wyrównane do dołu.
Delegat określa też, jak prośby pojawiają się na ekranie i z niego znikają. Więcej informacji znajdziesz w artykule o dostosowywaniu promptów.
Obsługa rozmiaru ekranu i trybu szerokiego
Aby obsługiwać zmiany wymiarów mapy i orientacji ekranu, delegat układu korzysta z tych funkcji:
Zmiana rozmiaru widoku: dostosowuje układ, gdy zmienia się fizyczny rozmiar mapy.
Tryb szeroki: przełącza się na wariant układu w formacie szerokim, gdy mapa jest wystarczająco szeroka.
Reagowanie na zmianę rozmiaru widoku
Układy podzielonego ekranu, suwaki kontenerów i obracanie urządzenia często wywołują wywołanie zwrotnesizeChanged(navMap:newState:). Użyj tego wywołania zwrotnego, aby wprowadzić ogólne dostosowania niestandardowego interfejsu użytkownika do różnych urządzeń. Zaimplementuj sizeChanged(navMap:newState:), aby zmienić położenie elementów niestandardowych w przypadku nowego formatu obrazu, zastosować własne niestandardowe punkty przerwania szerokości lub wysokości i wykryć, kiedy zdarzenie zmiany rozmiaru przełącza tryb szeroki, aby bezpiecznie zamienić warianty układu.
Informacje o trybie szerokim
Tryb szeroki aktywuje się, gdy kontener mapy jest wystarczająco szeroki, aby wyświetlać komponenty interfejsu obok siebie.
Odczytaj wartość logiczną wideMode z obiektu stanu, aby zmienić pozycję niestandardowych elementów interfejsu i zapewnić kierowcy widoczność środka mapy. Komponenty dostarczane przez Google, takie jak karta skrętu i karta szacowanego czasu dotarcia na miejsce, automatycznie zmniejszają się i zmieniają kształt w trybie szerokim, dlatego odczytanie tej wartości logicznej zapewnia, że układ dostosuje się dokładnie w tym samym momencie, w którym aktualizują się komponenty Google.
Poniżej znajdziesz przykłady pozycjonowania komponentów układu w trybie standardowym i szerokim:
Standardowy tryb portretowy: karta skrętu znajduje się u góry ekranu, a karta szacowanego czasu dotarcia na miejsce – u dołu.
Tryb szeroki: karta skrętu i karta szacowanego czasu dotarcia na miejsce powinny znajdować się przy krawędzi ekranu, przy czym karta skrętu powinna być u góry, a karta szacowanego czasu dotarcia na miejsce – u dołu. Umieszczenie którejkolwiek karty z dala od krawędzi natarcia powoduje niezdefiniowane zachowanie. Możesz umieścić widoczny obszar i własne treści w dowolnym miejscu. Często wybiera się stronę końcową kart.
Sprawdzanie trybu szerokiego podczas zmian stanu
Jeśli układ obsługuje wariant trybu szerokiego, w każdym wywołaniu zwrotnym przejścia stanu oceniaj wartość logiczną wideMode, zamiast polegać tylko na sizeChanged(navMap:newState:).
enableNavigationUI(navMap:newState:) i startGuidance(navMap:newState:oldState:), aby obsłużyć początkowe uruchomienia aplikacji w orientacji poziomej. Dzięki temu układ ocenia stan trybu szerokiego i aktywuje prawidłowy wariant układu standardowego lub szerokiego w momencie rozpoczęcia nowego stanu nawigacji.
Dostosowywanie do stylu Google
Aby ułatwić dopasowanie odstępów w widokach niestandardowych do komponentów dostarczanych przez Google, pakiet SDK udostępnia klasę NavigationStyleValues. Właściwości klasy zwracają pomiary w punktach.
Jeśli na przykład chcesz umieścić przycisk niestandardowy w górnym rogu ekranu naprzeciwko karty skrętu, jako górny margines przycisku użyj NavigationStyleValues.headerTopPadding. Przycisk zostanie wyrównany do górnej krawędzi karty skrętu.
Dostępne są te pomiary:
NavigationStyleValues.buttonHorizontalPaddingNavigationStyleValues.buttonVerticalPaddingNavigationStyleValues.defaultCornerRadiusNavigationStyleValues.elementHorizontalPaddingNavigationStyleValues.headerFooterHorizontalPaddingNavigationStyleValues.headerNominalHeightNavigationStyleValues.headerTopPadding
Widoki udostępniane przez Google zawierają też wbudowane marginesy. Gdy układasz 2 widoki dostarczone przez Google, ogranicz jeden do layoutMarginsGuide drugiego, aby ich marginesy się nie sumowały. Aby na przykład umieścić widżet prędkości nad kartą szacowanego czasu dotarcia na miejsce, ogranicz dolną kotwicę speedWidget do etaCard.layoutMarginsGuide.topAnchor.
Dostosowywanie promptów
Delegat układu określa, jak prompty pojawiają się na ekranie i z niego znikają oraz które ich rogi są zaokrąglone.
Animowane prompty
Pakiet SDK nie animuje próśb. Przedstawiciel decyduje, jak każdy prompt ma się pojawiać i znikać:
- W programie
showPrompt(navMap:newPrompt:)dodaj prompt do widoku mapy w pozycji początkowej, a następnie animuj go, aby przesunął się na właściwe miejsce. Możesz na przykład rozpocząć prośbę tuż pod widocznym obszarem lub za planszą dolną, wyrównując jej górną krawędź do górnej krawędzi planszy. - W metodzie
hidePrompt(navMap:oldPrompt:)animuj prośbę, aby zniknęła z pola widzenia, a następnie usuń ją z widoku nadrzędnego po zakończeniu animacji. - W funkcji
changePrompt(navMap:newPrompt:oldPrompt:)uruchom jednocześnie animacje ukrywania i pokazywania, aby zastąpić jeden prompt innym.
Takie podejście jest stosowane w przykładowej implementacji.
Aby wyłączyć prośby o informacje o natężeniu ruchu, lepszych trasach i zdarzeniach, ustaw właściwość shouldDisplayPrompts obiektu GMSNavigator na false.
Zaokrąglanie rogów prompta
Prompty są zgodne z protokołem NavigationRoundedCornerView, więc możesz wybrać, które ich rogi mają być zaokrąglone. Domyślnie zaokrąglone są tylko górne rogi, co pasuje do prośby zakotwiczonej przy dolnej krawędzi ekranu.
Jeśli umieścisz prompt nad innymi treściami, np. planszą dolną, zaokrąglij wszystkie 4 rogi:
newPrompt.roundedCorners = [
.layerMinXMinYCorner, .layerMaxXMinYCorner,
.layerMinXMaxYCorner, .layerMaxXMaxYCorner,
]
Przykład: implementacja układu opartego na ograniczeniach
Poniższy przykład pokazuje podstawowy delegat układu, który używa ograniczeń automatycznego układu do pozycjonowania karty skrętu, karty szacowanego czasu dotarcia na miejsce, przycisków, logo Google i widocznego obszaru. Możesz też przesuwać instrukcje na ekranie i poza niego. Aby przykład był krótki, nie obsługuje on trybu szerokiego, widżetu prędkości ani paska postępu przejazdu, więc nadaje się do aplikacji, które obsługują tylko orientację pionową.
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)
}
}
Aby użyć obiektu delegowanego, ustaw go w widoku mapy przed włączeniem nawigacji, jak opisano na liście kontrolnej podstawowych czynności.
Ograniczenia i znane problemy
Dostosowywanie układu nie dotyczy CarPlay, który używa szablonów o stałym układzie. Jeśli wywołasz metodę setLayoutDelegate(_:) w widoku mapy utworzonym na potrzeby ekranu CarPlay, metoda zwróci błąd.
Problemy, które występują w konkretnych wersjach pakietu SDK, znajdziesz w sekcji Znane problemy w informacjach o wersji.