Menyesuaikan tata letak navigasi

Contoh animasi yang menampilkan tata letak navigasi yang disesuaikan di iOS

Catatan: Dokumen ini menggunakan konvensi penamaan Swift untuk class, protokol, dan API.

Gunakan penyesuaian tata letak untuk mengontrol hierarki tampilan dan penempatan layar untuk tampilan yang disediakan Google dan tampilan bisnis kustom Anda sendiri.

Daripada menambahkan tampilan kustom ke slot tata letak yang telah ditentukan, terapkan delegasi tata letak untuk mengelola tata letak seluruh layar. Selama transisi status navigasi, delegasi tata letak Anda akan menerima komponen yang disediakan Google, seperti kartu belokan, kartu PWT, dan tombol. Posisikan elemen ini bersama konten kustom Anda sendiri menggunakan batasan Tata Letak Otomatis iOS standar.

Gunakan framework ini untuk memosisikan informasi bisnis kustom—seperti status pesanan atau petunjuk pengambilan—dengan tepat di layar sekaligus mencegah tampilan Anda tumpang-tindih.

Cara kerja penyesuaian tata letak

Penyesuaian tata letak menggunakan pola desain delegasi. Alih-alih otomatis menggambar atau memosisikan komponen UI di layar, SDK meneruskannya langsung ke class kustom yang Anda tulis: delegasi tata letak.

Untuk menyesuaikan tata letak, buat class yang sesuai dengan protokol NavigationLayoutDelegate dan teruskan instance ke metode setLayoutDelegate(_:) dari GMSMapView Anda. Setiap kali status navigasi berubah—seperti berpindah dari peta dasar ke panduan belokan demi belokan aktif—SDK akan menjalankan metode callback pada delegasi Anda dan menyediakan komponen UI yang tersedia untuk status tertentu tersebut.

Untuk memberikan pengalaman pengguna yang lancar dan terintegrasi, framework ini menetapkan pemisahan tanggung jawab yang jelas antara aplikasi Anda dan SDK. Gunakan delegasi tata letak untuk menyesuaikan hal berikut:

  • Membangun hierarki tampilan: Pilih dengan tepat komponen Google dan tampilan bisnis kustom yang akan ditambahkan ke layar untuk setiap status UI.
  • Posisikan setiap elemen: Tetapkan anchor layar, margin, dan penempatan tata letak yang tepat. Hindari menerapkan batasan lebar atau tinggi kustom ke komponen Google karena komponen Google menghitung dimensi internalnya sendiri.
  • Membingkai peta dasar: Tentukan batas yang terlihat dari kamera peta menggunakan koordinat komponen area pandang.
  • Lapisi layar: Tentukan apakah tampilan kustom akan mengambang di atas, di bawah, atau di samping kontrol bawaan Google.

Sementara itu, Anda tidak dapat menyesuaikan komponen Google berikut dengan delegasi tata letak:

  • Dimensi komponen: Ukuran dan dimensi internal komponen yang disediakan Google, yang dihitung secara otomatis oleh SDK.
  • Kondisi pemicu: Saat pemberitahuan atau perintah dinamis muncul berdasarkan data rute real-time.

Prinsip penerapan

Saat menulis delegasi tata letak, perhatikan aturan berikut untuk mencegah bug tata letak atau error runtime:

  • Defaultnya adalah layar kosong: Komponen yang disediakan Google hanya muncul jika delegasi Anda secara eksplisit menambahkannya ke hierarki tampilan dan memosisikannya.
  • API lama tidak didukung: Properti dan metode yang mengontrol model tata letak berbasis slot lama tidak akan didukung dan mungkin tidak berperilaku seperti yang diharapkan saat delegasi kustom aktif.
  • Jangan ubah struktur tampilan internal: Jangan melintasi atau mengubah hierarki tampilan komponen yang disediakan Google (seperti kartu belokan atau kartu PWT). Karena hierarki tampilan internal ini adalah detail implementasi yang mendasarinya, hierarki tersebut dapat berubah di seluruh rilis SDK. Mengubahnya dapat menyebabkan tata letak Anda rusak selama update SDK mendatang.

Kompatibilitas API lama

Untuk perilaku tata letak yang andal saat menggunakan delegasi tata letak kustom, hindari penggunaan API berbasis slot lama berikut, yang direncanakan untuk dihentikan penggunaannya. Untuk memigrasikan aplikasi yang ada, ganti penggunaan API ini dengan kode di delegasi tata letak kustom Anda:

Menampilkan API berbasis slot lama

API Lama Penggantian Layout Delegate
isNavigationHeaderEnabled Baca tampilan turnCard di startGuidance(navMap:newState:oldState:).
isNavigationFooterEnabled Baca tampilan etaCard di startGuidance(navMap:newState:oldState:).
isNavigationTripProgressBarEnabled Baca tampilan tripProgressBar di startGuidance(navMap:newState:oldState:).
isNavigationReportIncidentButtonEnabled Temukan tombol .reporting dalam array buttons.
setHeaderAccessoryView(_:) dan invalidateLayout(forAccessoryView:) Tambahkan tampilan Anda sendiri ke tata letak dan tautkan ke turnCard.
padding dan paddingAdjustmentBehavior Posisikan tampilan viewport untuk membingkai kamera.
navigationHeaderLayoutGuide Tautkan tampilan kustom Anda langsung ke turnCard.
navigationFooterLayoutGuide Tautkan tampilan kustom Anda langsung ke etaCard.
bottomTrailingButtonsLayoutGuide Tautkan tombol Anda langsung ke panduan etaCard atau penampung.
bottomTrailingButtonsAlternateLayoutGuide Tautkan tombol Anda langsung ke tombol .reporting atau batasan wideMode.
navigatorWillPresentPrompt(_:) dan navigatorDidDismissPrompt(_:) Mengandalkan callback perintah NavigationLayoutDelegate, seperti showPrompt(navMap:newPrompt:).

Saat delegasi tata letak kustom ditetapkan, panduan tata letak lama (seperti navigationHeaderLayoutGuide atau bottomTrailingButtonsLayoutGuide) dan metode tampilan aksesori header tidak akan berpengaruh. Sebagai gantinya, kaitkan tampilan Anda ke tampilan yang disediakan Google.

Properti compassButton dari GMSUISettings masih mengontrol kompas saat UI navigasi dinonaktifkan. Saat navigasi diaktifkan, delegasi Anda akan memutuskan apakah akan menampilkan kompas dari array buttons atau tidak.

Checklist Essentials

Ikuti langkah-langkah dan persyaratan penting berikut untuk berhasil menerapkan delegasi tata letak:

  • Tetapkan delegasi sebelum mengaktifkan navigasi: Panggil setLayoutDelegate(_:) di GMSMapView sebelum Anda mengaktifkan navigasi dengan isNavigationEnabled atau enableNavigation(with:). Metode ini akan menampilkan error jika navigasi sudah diaktifkan atau jika Anda membuat tampilan peta untuk layar CarPlay. Tampilan peta menyimpan referensi yang kuat ke delegasi Anda. Untuk memulihkan tata letak bawaan, teruskan nil saat navigasi dinonaktifkan.

    // 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
        
  • Lampirkan tampilan wajib: Anda harus menambahkan logo Google (googleLogo) dan Area pandang (viewport) ke penampung tata letak di semua status navigasi.

  • Mematuhi dimensi tetap: SDK menentukan dimensi untuk beberapa komponen yang disediakan Google. Jangan terapkan batasan lebar atau tinggi kustom ke elemen berikut:

    • Kartu petunjuk navigasi
    • kartu PWT
    • Perintah masuk
    • Logo Google
    • Widget kecepatan
  • Tambahkan elemen sebagai sub-tampilan: Tambahkan tampilan yang disediakan Google langsung sebagai sub-tampilan ke tampilan penampung kustom atau ke GMSMapView itu sendiri.

  • Sejajarkan menggunakan panduan tata letak: Tampilan yang disediakan Google menyertakan margin visual bawaan. Untuk menyelaraskan elemen kustom Anda secara sempurna dengan komponen yang disediakan Google, batasi tampilan Anda ke properti layoutMarginsGuide tampilan Google.

  • Pembersihan saat keluar dari status: Di stopGuidance(navMap:newState:oldState:), hapus tampilan panduan aktif dan nonaktifkan batasannya sebelum Anda memulihkan tata letak siap navigasi. Di disableNavigationUI(navMap:oldState:), hapus semua tampilan dan panduan tata letak yang Anda tambahkan, termasuk perintah apa pun di layar. Tindakan ini mencegah tampilan dari satu status tumpang-tindih dengan status berikutnya.

  • Miliki aksesibilitas dan dukungan kanan ke kiri: Tampilan yang disediakan Google memenuhi standar aksesibilitas, tetapi delegasi Anda bertanggung jawab atas aksesibilitas tata letak secara keseluruhan. Untuk mendukung bahasa yang ditulis dari kanan ke kiri, gunakan anchor awal dan akhir, bukan anchor kiri dan kanan.

  • Hindari API tata letak lama: Jangan gunakan properti pengubah tata letak lama di GMSUISettings atau panduan tata letak lama di GMSMapView saat delegasi kustom aktif. SDK mungkin mengabaikan panggilan lama ini saat delegasi kustom dilampirkan.

Status UI dan komponen Google

Saat transisi status navigasi terjadi, SDK akan meneruskan objek NavigationUIState hanya baca ke callback delegasi Anda. Objek ini menggabungkan flag konfigurasi tata letak saat ini dengan komponen Google (misalnya, instance UIView) yang Anda perlukan untuk merender layar.

Delegasi tata letak mengelola hierarki tampilan di empat status operasional. Diagram status berikut mengilustrasikan cara SDK bertransisi di antara status navigasi dan metode callback yang dijalankannya pada delegasi Anda:

Diagram transisi status siklus proses NavigationLayoutDelegate yang menampilkan transisi antara status Non-Navigasi, Siap Navigasi, dan Panduan Aktif.

Komponen Google tertentu yang disediakan untuk delegasi Anda bergantung pada fase siklus proses navigasi saat ini.

Komponen yang diperlukan (semua status navigasi)

Terlepas dari fase navigasi saat ini, Anda harus menyertakan, memosisikan, dan menjaga komponen Google berikut tetap terlihat dalam hierarki tampilan Anda:

  • Logo Google (googleLogo): Komponen ini menampilkan logo Google Maps wajib. Jika tombol Pusatkan ulang diaktifkan, logo akan otomatis berubah menjadi tombol ini saat pengemudi men-scroll peta menjauh dari kendaraannya. Akibatnya, penempatan logo Anda menentukan tempat tombol Pusatkan ulang muncul. Sebaiknya tempatkan logo di sudut kiri bawah tata letak agar sesuai dengan ekspektasi UI peta standar.

  • Area pandang (viewport): UIView tidak terlihat yang menentukan batas penyesuaian frame kamera. Penempatan area pandang menentukan dengan tepat tempat SDK memusatkan chevron kendaraan dan menggambar garis rute aktif. Posisikan area pandang agar menutupi area layar yang terbuka dan tidak terhalang, sehingga aman dari overlay buram seperti panel bawah kustom.

Selama status siap navigasi, UI tetap minimal untuk mempertahankan fokus pada peta dasar. Objek NavigationReadyUIState Anda memberikan akses ke hal berikut:

  • buttons: Array tampilan tindakan mengambang yang dikonfigurasi untuk peta dasar (biasanya hanya tombol Kompas). Karena Google mungkin menambahkan atau menyusun ulang tombol dalam rilis SDK mendatang, hindari mengandalkan indeks array tetap. Sebagai gantinya, lakukan iterasi melalui array, baca jenis setiap tombol dengan membandingkan properti type-nya dengan NavigationButtonKnownType.compass, dan ekstrak tampilan fisik dengan memanggil properti view-nya. Perhatikan bahwa tombol yang tepat dalam array ini berbeda dengan tombol yang tersedia selama panduan aktif.

Komponen panduan aktif

Saat panduan belokan demi belokan dimulai, SDK akan membuka rangkaian lengkap kontrol navigasi. Objek ActiveGuidanceUIState Anda memberikan akses ke komponen Google berikut:

  • turnCard: Banner header utama yang menampilkan petunjuk manuver mendatang, pengukuran jarak, dan panduan jalur. Tempatkan di bagian atas tata letak untuk membuat hierarki navigasi yang sudah dikenal dan menambatkan tampilan kustom di sekitarnya.

  • etaCard: Banner footer yang menampilkan perkiraan waktu tiba, sisa waktu perjalanan, dan sisa jarak ke tujuan. Tempatkan di sepanjang tepi bawah layar, atau integrasikan koordinatnya dengan sheet pengelolaan tugas kustom Anda.

  • tripProgressBar: Status progres vertikal yang menunjukkan kepada pengemudi seberapa jauh mereka telah melakukan perjalanan di sepanjang rute saat ini. Tidak seperti tata letak lama yang menetapkan ini secara ketat ke tepi peta, Anda memiliki kebebasan penuh untuk menambatkannya di mana saja—misalnya, di sepanjang tepi samping penampung kustom.

  • speedWidget: Kontrol mengambang untuk speedometer dan batas kecepatan yang diposting. Bergantung pada setelan API Anda (properti shouldDisplaySpeedometer dan shouldDisplaySpeedLimit dari GMSMapView) dan ketersediaan data, tampilan ini akan diubah ukurannya secara dinamis di antara empat status visual saat runtime (tidak menampilkan apa pun, hanya kecepatan saat ini, hanya batas kecepatan, atau kedua pembacaan). Karena widget dapat beralih di antara ukuran ini tanpa pemberitahuan, selalu jangkar tampilan di sekitarnya menggunakan batasan sehingga tata letak Anda otomatis beradaptasi untuk mencegah tumpang-tindih spasial.

  • buttons: Array tampilan tindakan mengambang yang diperluas untuk status panduan aktif (biasanya mencakup tombol Kompas dan Pelaporan Insiden). Sama seperti dalam status siap navigasi, Anda dapat menemukan dan mengekstrak tampilan tombol satu per satu dengan memfilter array menurut type (.compass atau .reporting) dan mengekstrak tampilan fisik menggunakan properti view-nya. Anda kemudian dapat memosisikannya secara terpisah, atau menambahkannya ke GMSNavigationHidingStackView. Tampilan stack ini mengatur tombol secara vertikal dan, saat ruang habis, menyembunyikannya mulai dari akhir array arrangedSubviews. Jangan tetapkan isHidden pada tampilan yang dikelolanya.
Tips: Praktik terbaik untuk framing kamera. Tampilan kartu belokan memiliki tinggi tetap berdasarkan NavigationStyleValues.headerNominalHeight, yang cukup tinggi untuk konten kartu belokan apa pun. Agar framing kamera tetap stabil, batasi bagian atas viewport langsung ke tepi bawah turnCard, seperti yang ditunjukkan dalam contoh implementasi.

Komponen perintah dinamis

Perintah, seperti pemberitahuan insiden atau peringatan kamera pemantau, dipicu secara independen selama panduan aktif.

Saat perintah siap ditampilkan, SDK akan memanggil callback showPrompt(navMap:newPrompt:) delegasi Anda dan meneruskan tampilan newPrompt secara langsung. Delegasi Anda bertanggung jawab untuk memosisikan perintah ini dengan lancar di atas tata letak Anda (biasanya ditambatkan ke tepi bawah penampung peta).

Karena perintah yang masuk mencakup bagian bawah layar, Anda harus memperbarui tata letak untuk mencegah perintah tumpang-tindih dengan area pandang, logo Google, atau tombol apa pun yang sejajar di bagian bawah.

Delegasi Anda juga mengontrol cara perintah ditampilkan dan ditutup di layar. Untuk mengetahui detailnya, lihat Menyesuaikan perintah.

Menangani ukuran layar dan mode lebar

Untuk menangani perubahan dimensi peta dan orientasi layar, delegasi tata letak menggunakan fitur berikut:

  • Pengubahan ukuran tampilan: Menyesuaikan tata letak setiap kali ukuran fisik peta berubah.

  • Mode lebar: Beralih ke varian tata letak format lebar jika peta cukup lebar.

Merespons pengubahan ukuran tampilan

Tata letak layar terpisah, penggeser penampung, dan rotasi perangkat biasanya memicu callback sizeChanged(navMap:newState:). Gunakan callback ini untuk melakukan penyesuaian responsif umum pada UI kustom Anda. Terapkan sizeChanged(navMap:newState:) untuk memosisikan ulang elemen kustom untuk rasio aspek layar baru, terapkan titik henti sementara lebar atau tinggi kustom Anda sendiri, dan deteksi saat peristiwa pengubahan ukuran mengalihkan mode lebar untuk menukar varian tata letak dengan aman.

Memahami mode lebar

Mode lebar diaktifkan saat penampung peta cukup lebar untuk menampilkan komponen UI secara berdampingan.

Baca boolean wideMode dari objek status untuk memosisikan ulang elemen UI kustom Anda dan menjaga bagian tengah peta tetap terlihat jelas bagi pengemudi. Karena komponen yang disediakan Google seperti kartu belokan dan kartu PWT otomatis menyusut dan berubah bentuk dalam mode lebar, membaca boolean ini akan memastikan tata letak Anda beradaptasi pada saat yang sama persis dengan pembaruan komponen Google.

Pertimbangkan contoh berikut untuk memosisikan komponen tata letak dalam mode standar dan lebar:

  • Mode potret standar: Posisikan kartu belokan di bagian atas layar dan kartu PWT di bagian bawah.

  • Mode lebar: Pertahankan kartu belokan dan kartu PWT di sepanjang tepi depan layar, dengan kartu belokan di bagian atas dan kartu PWT di bagian bawah. Menempatkan salah satu kartu jauh dari tepi depan akan menghasilkan perilaku yang tidak ditentukan. Anda dapat menempatkan area pandang dan konten Anda sendiri di mana saja; pilihan yang umum adalah sisi akhir kartu.

Memeriksa mode lebar selama transisi status

Jika tata letak Anda mendukung variasi mode lebar, evaluasi boolean wideMode di dalam setiap callback transisi status, bukan hanya mengandalkan sizeChanged(navMap:newState:).

Periksa objek status selama callback seperti enableNavigationUI(navMap:newState:) dan startGuidance(navMap:newState:oldState:) untuk menangani peluncuran aplikasi awal dalam orientasi lanskap. Pendekatan ini memastikan bahwa tata letak Anda mengevaluasi status mode lebar dan mengaktifkan varian tata letak standar atau lebar yang benar saat status navigasi baru dimulai.

Menyesuaikan dengan gaya Google

Untuk membantu tampilan kustom Anda menyesuaikan spasi komponen yang disediakan Google, SDK menyediakan class NavigationStyleValues. Properti class-nya menampilkan pengukuran dalam poin.

Misalnya, untuk menempatkan tombol kustom di sudut atas layar di seberang kartu belokan, gunakan NavigationStyleValues.headerTopPadding sebagai margin atas tombol. Tombol kemudian akan sejajar dengan tepi atas kartu belokan.

Pengukuran berikut tersedia:

  • NavigationStyleValues.buttonHorizontalPadding
  • NavigationStyleValues.buttonVerticalPadding
  • NavigationStyleValues.defaultCornerRadius
  • NavigationStyleValues.elementHorizontalPadding
  • NavigationStyleValues.headerFooterHorizontalPadding
  • NavigationStyleValues.headerNominalHeight
  • NavigationStyleValues.headerTopPadding

Tampilan yang disediakan Google juga menyertakan margin bawaan. Saat Anda menumpuk dua tampilan yang disediakan Google, batasi satu tampilan ke layoutMarginsGuide tampilan lainnya agar marginnya tidak bertambah. Misalnya, untuk menempatkan widget kecepatan di atas kartu PWT, batasi anchor bawah speedWidget ke etaCard.layoutMarginsGuide.topAnchor.

Menyesuaikan perintah

Delegasi tata letak Anda mengontrol cara perintah ditampilkan dan ditutup di layar, serta sudut mana yang dibulatkan.

Perintah animasi

SDK tidak menganimasikan perintah untuk Anda. Delegasi Anda menentukan cara setiap perintah muncul dan menghilang:

  • Di showPrompt(navMap:newPrompt:), tambahkan perintah ke tampilan peta pada posisi awal, lalu animasikan ke tempatnya. Misalnya, mulai perintah tepat di bawah area yang terlihat, atau di belakang panel bawah dengan tepi atasnya sejajar dengan bagian atas panel.
  • Di hidePrompt(navMap:oldPrompt:), animasikan perintah agar tidak terlihat, lalu hapus dari superview-nya saat animasi selesai.
  • Di changePrompt(navMap:newPrompt:oldPrompt:), jalankan animasi sembunyikan dan tampilkan secara bersamaan untuk mengganti satu perintah dengan perintah lainnya.

Contoh implementasi menggunakan pendekatan ini.

Untuk menonaktifkan perintah terkait lalu lintas, rute yang lebih baik, dan insiden, tetapkan properti shouldDisplayPrompts dari GMSNavigator ke false.

Membulatkan sudut perintah

Prompt sesuai dengan protokol NavigationRoundedCornerView, sehingga Anda dapat memilih sudut mana yang dibulatkan. Secara default, perintah hanya membulatkan sudut atasnya, yang sesuai untuk perintah yang ditambatkan ke tepi bawah layar. Jika Anda menempatkan perintah di atas konten lain, seperti panel bawah, bulatkan keempat sudutnya:

newPrompt.roundedCorners = [
  .layerMinXMinYCorner, .layerMaxXMinYCorner,
  .layerMinXMaxYCorner, .layerMaxXMaxYCorner,
]

Contoh: Implementasi tata letak berbasis batasan

Contoh berikut menunjukkan delegasi tata letak dasar yang menggunakan batasan Tata Letak Otomatis untuk memosisikan kartu belokan, kartu PWT, tombol, logo Google, dan area pandang. Fitur ini juga menggeser perintah ke dalam dan ke luar layar. Agar contoh tetap singkat, contoh ini tidak menangani mode lebar, widget kecepatan, atau status progres perjalanan, sehingga cocok untuk aplikasi yang hanya mendukung orientasi potret.

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)
  }
}

Untuk menggunakan delegasi, tetapkan di tampilan peta sebelum Anda mengaktifkan navigasi, seperti yang dijelaskan dalam checklist Hal-hal penting.

Batasan dan masalah umum

Penyesuaian tata letak tidak berlaku untuk CarPlay, yang menggunakan template tata letak tetap. Jika Anda memanggil setLayoutDelegate(_:) pada tampilan peta yang Anda buat untuk layar CarPlay, metode ini akan menampilkan error.

Untuk masalah yang memengaruhi versi SDK tertentu, lihat Masalah umum dalam catatan rilis.