
Hem Google tarafından sağlanan görünümler hem de kendi özel işletme görünümleriniz için görünüm hiyerarşisini ve ekran yerleşimini kontrol etmek üzere düzen özelleştirmeyi kullanın.
Önceden tanımlanmış düzen alanlarına özel görünümler eklemek yerine, tüm ekranın düzenini yönetmek için bir düzen temsilcisi uygulayın. Navigasyon durumu geçişleri sırasında düzen temsilciniz, Google tarafından sağlanan bileşenleri (ör. rota kartı, TVS kartı ve düğmeler) alır. Bu öğeleri, standart iOS Otomatik Düzen kısıtlamalarını kullanarak kendi özel içeriğinizin yanına yerleştirin.
Sipariş durumu veya teslim alma talimatları gibi özel işletme bilgilerini ekranda tam olarak konumlandırmak ve görünümlerinizin çakışmasını önlemek için bu çerçeveyi kullanın.
Düzen özelleştirmenin işleyiş şekli
Düzen özelleştirmede yetki verme tasarım deseni kullanılır. SDK, kullanıcı arayüzü bileşenlerini ekranda otomatik olarak çizmek veya konumlandırmak yerine doğrudan yazdığınız özel bir sınıfa (düzen temsilcisi) iletir.
Düzeninizi özelleştirmek içinNavigationLayoutDelegate protokolüne uygun bir sınıf oluşturun ve GMSMapView öğenizin setLayoutDelegate(_:) yöntemine bir örnek iletin. Navigasyon durumu her değiştiğinde (ör. temel haritadan aktif adım adım rehberliğe geçiş) SDK, temsilcinizdeki geri çağırma yöntemlerini yürütür ve söz konusu durum için kullanılabilen kullanıcı arayüzü bileşenlerini sağlar.
Bu çerçeve, sorunsuz ve entegre bir kullanıcı deneyimi sunmak için uygulamanız ile SDK arasında net bir sorumluluk ayrımı oluşturur. Aşağıdakileri özelleştirmek için düzen temsilcisini kullanın:
- Görünüm hiyerarşisini oluşturma: Her kullanıcı arayüzü durumu için ekrana hangi Google bileşenlerinin ve özel işletme görünümlerinin ekleneceğini seçin.
- Her öğeyi konumlandırın: Ekran sabitleyicileri, kenar boşluklarını ve düzen yerleşimini hassas bir şekilde ayarlayın. Google bileşenleri kendi iç boyutlarını hesapladığından, Google bileşenlerine özel genişlik veya yükseklik kısıtlamaları uygulamaktan kaçının.
- Temel haritayı çerçeveleme: Görüntü alanı bileşeninin koordinatlarını kullanarak harita kamerasının görünür sınırlarını tanımlayın.
- Ekranı katmanlara ayırın: Özel görünümlerin Google'ın yerleşik kontrollerinin üstünde, altında veya yanında yer alıp almayacağına karar verin.
Aşağıdaki Google bileşenlerini ise düzen temsilcisiyle özelleştiremezsiniz:
- Bileşen boyutları: Google tarafından sağlanan bileşenlerin SDK tarafından otomatik olarak hesaplanan boyutu ve dahili boyutları.
- Tetikleme koşulları: Dinamik uyarıların veya istemlerin gerçek zamanlı rota verilerine göre gösterildiği durumlar.
Uygulama ilkeleri
Düzen temsilcisi yazarken düzen hatalarını veya çalışma zamanı kilitlenmelerini önlemek için aşağıdaki kuralları göz önünde bulundurun:
- Varsayılan olarak boş bir ekran gösterilir: Google tarafından sağlanan bileşenler yalnızca temsilciniz bunları görünüm hiyerarşisine açıkça ekleyip konumlandırdığında görünür.
- Eski API'ler desteklenmez: Eski alan tabanlı düzen modelini kontrol eden özellikler ve yöntemler desteklenmez ve özel bir temsilci etkin olduğunda beklendiği gibi davranmayabilir.
- Dahili görünüm yapılarını değiştirmeyin: Google tarafından sağlanan bileşenlerin (ör. rota kartı veya TVS kartı) görünüm hiyerarşilerini incelemeyin veya değiştirmeyin. Bu dahili görünüm hiyerarşileri, temel uygulama ayrıntıları olduğundan SDK sürümlerine göre değişiklik gösterebilir. Bunları değiştirmek, gelecekteki SDK güncellemeleri sırasında düzeninizin bozulmasına neden olabilir.
Eski API uyumluluğu
Özel bir düzen temsilcisi kullanırken güvenilir düzen davranışı için, desteğinin sonlandırılması planlanan aşağıdaki eski yuvaya dayalı API'leri kullanmaktan kaçının. Mevcut bir uygulamayı taşımak için bu API'lerin kullanımını özel düzen temsilcinizdeki kodla değiştirin:
Eski alan tabanlı API'leri göster
| Eski API | Layout Delegate replacement |
|---|---|
isNavigationHeaderEnabled |
startGuidance(navMap:newState:oldState:) dilinde turnCard görünümünü okuyun. |
isNavigationFooterEnabled |
startGuidance(navMap:newState:oldState:) dilinde etaCard görünümünü okuyun. |
isNavigationTripProgressBarEnabled |
startGuidance(navMap:newState:oldState:) dilinde tripProgressBar görünümünü okuyun. |
isNavigationReportIncidentButtonEnabled |
buttons dizisindeki .reporting düğmesini bulun. |
setHeaderAccessoryView(_:) ve
invalidateLayout(forAccessoryView:) |
Düzene kendi görünümünüzü ekleyin ve turnCard öğesine sabitleyin. |
padding ve paddingAdjustmentBehavior |
viewport görünümünü kamerayı çerçeveleyecek şekilde konumlandırın. |
navigationHeaderLayoutGuide |
Özel görünümlerinizi doğrudan turnCard sabitleyin. |
navigationFooterLayoutGuide |
Özel görünümlerinizi doğrudan etaCard sabitleyin. |
bottomTrailingButtonsLayoutGuide |
Düğmelerinizi doğrudan etaCard veya kapsayıcı kılavuzlarına sabitleyin. |
bottomTrailingButtonsAlternateLayoutGuide |
Düğmelerinizi doğrudan .reporting düğmesine veya wideMode kısıtlamalarına sabitleyin. |
navigatorWillPresentPrompt(_:) ve
navigatorDidDismissPrompt(_:) |
showPrompt(navMap:newPrompt:) gibi NavigationLayoutDelegate istem geri çağırmalarınızı kullanın. |
Özel bir düzen temsilcisi ayarlandığında eski düzen kılavuzları (ör. navigationHeaderLayoutGuide veya bottomTrailingButtonsLayoutGuide) ve başlık aksesuar görünümü yöntemlerinin hiçbir etkisi olmaz. Bunun yerine, görünümlerinizi Google tarafından sağlanan görünümlere sabitleyin.
Navigasyon kullanıcı arayüzü devre dışı bırakıldığında GMSUISettings öğesinin compassButton özelliği pusulayı kontrol etmeye devam eder. Navigasyon etkinken temsilciniz, pusulanın buttons dizisinden gösterilip gösterilmeyeceğine karar verir.
Essentials kontrol listesi
Düzen temsilcinizi başarıyla uygulamak için aşağıdaki temel adımları ve şartları uygulayın:
-
Navigasyonu etkinleştirmeden önce temsilciyi ayarlayın:
isNavigationEnabledveyaenableNavigation(with:)ile navigasyonu etkinleştirmeden önceGMSMapViewüzerindesetLayoutDelegate(_:)yöntemini çağırın. Navigasyon zaten etkinleştirilmişse veya CarPlay ekranı için harita görünümü oluşturduysanız yöntem bir hata verir. Harita görünümü, temsilcinize güçlü bir referans sağlar. Yerleşik düzeni geri yüklemek için gezinme devre dışıykennildeğerini iletin.// 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
-
Zorunlu görünümleri ekleyin: Tüm navigasyon durumlarında düzen kapsayıcınıza Google logosunu (
googleLogo) ve görüntü alanını (viewport) eklemeniz gerekir. -
Sabit boyutlara uyma: SDK, Google tarafından sağlanan çeşitli bileşenlerin boyutlarını belirler. Aşağıdaki öğelere özel genişlik veya yükseklik kısıtlamaları uygulamayın:
- Rota kartı
- TVS kartı
- Gelen istemler
- Google logosu
- Hız widget'ı
-
Öğeleri alt görünüm olarak ekleme: Google tarafından sağlanan görünümleri doğrudan özel kapsayıcı görünümünüze veya
GMSMapViewöğesine alt görünüm olarak ekleyin. -
Düzen kılavuzlarını kullanarak hizalama: Google tarafından sağlanan görünümler, yerleşik görsel kenar boşlukları içerir. Özel öğelerinizi Google tarafından sağlanan bileşenlerle mükemmel bir şekilde hizalamak için görünümlerinizi Google görünümünün
layoutMarginsGuideözelliğiyle sınırlandırın. -
Durumdan çıkışta temizleme:
stopGuidance(navMap:newState:oldState:)içinde, navigasyona hazır düzeninizi geri yüklemeden önce etkin kılavuz görünümlerini kaldırın ve kısıtlamalarını devre dışı bırakın.disableNavigationUI(navMap:oldState:)uygulamasında, eklediğiniz tüm görünümleri ve düzen kılavuzlarını (ekrandaki istemler dahil) kaldırın. Bu, bir durumdaki görünümlerin bir sonraki durumdaki görünümlerle çakışmasını önler. -
Erişilebilirlik ve sağdan sola desteği: Google tarafından sağlanan görünümler erişilebilirlik standartlarını karşılar ancak genel düzenin erişilebilirliğinden temsilciniz sorumludur. Sağdan sola dilleri desteklemek için sol ve sağ bağlantılar yerine başta ve sonda bulunan bağlantıları kullanın.
-
Eski düzen API'lerinden kaçının: Özel bir temsilci etkinken
GMSUISettingsüzerinde eski düzen değiştirme özelliklerini veyaGMSMapViewüzerinde eski düzen kılavuzlarını kullanmayın. Özel bir temsilci eklendiğinde SDK bu eski görüşme özelliği çağrılarını yoksayabilir.
Kullanıcı arayüzü durumları ve Google bileşenleri
Bir navigasyon durumu geçişi gerçekleştiğinde SDK, temsilci geri çağırma işlevinize salt okunur bir NavigationUIState nesnesi iletir. Bu nesne, mevcut düzen yapılandırma işaretlerini ekranı oluşturmak için gereken Google bileşenleriyle (ör. UIView örnekleri) birleştirir.
Düzen temsilcisi, görünüm hiyerarşisini dört çalışma durumunda yönetir. Aşağıdaki durum diyagramı, SDK'nın gezinme durumları arasında nasıl geçiş yaptığını ve temsilcinizde hangi geri çağırma yöntemlerini yürüttüğünü gösterir:
Temsilcinize sağlanan belirli Google bileşenleri, navigasyon yaşam döngüsünün mevcut aşamasına bağlıdır.
Gerekli bileşenler (tüm gezinme durumları)
Mevcut gezinme aşamasından bağımsız olarak, aşağıdaki Google bileşenlerini görünüm hiyerarşinize eklemeniz, konumlandırmanız ve görünür tutmanız gerekir:
Google Logosu (
googleLogo): Bu bileşen, zorunlu Google Haritalar logosunu gösterir. Yeniden ortala düğmesi etkinleştirilirse sürücü haritayı aracından uzaklaştırdığında logo otomatik olarak bu düğmeye dönüşür. Bu nedenle, logonun yerleşimi Yeniden ortala düğmesinin nerede görüneceğini belirler. Standart harita kullanıcı arayüzü beklentilerini karşılamak için logoyu düzeninizin sol alt köşesine yerleştirmeniz önerilir.Görüntü alanı (
viewport): Kameranın kadraj sınırlarını tanımlayan görünmez birUIView. Görüntü alanını yerleştirdiğiniz yer, SDK'nın araç yön işaretini tam olarak nerede ortalayacağını ve etkin rota çizgisini nerede çizeceğini belirler. Görüntü alanını, ekranınızın açık ve engellenmemiş alanını kaplayacak şekilde konumlandırın. Böylece, özel alt sayfalar gibi opak yer paylaşımlarından güvenli bir şekilde kaçınabilirsiniz.
Gezinmeye hazır bileşenler
Navigasyona hazır durumdayken kullanıcı arayüzü, temel haritaya odaklanmayı sürdürmek için minimum düzeyde kalır. NavigationReadyUIState nesneniz aşağıdakilere erişim sağlar:
- buttons: Temel harita için yapılandırılmış kayan işlem görünümleri dizisi (genellikle yalnızca Pusula düğmesi). Google, gelecekteki SDK sürümlerinde düğme ekleyebileceği veya düğmeleri yeniden sıralayabileceği için sabit dizi dizinlerine güvenmeyin. Bunun yerine, dizideki her düğmeyi tek tek inceleyin,
typeözelliğiniNavigationButtonKnownType.compassile karşılaştırarak her düğmenin türünü okuyun veviewözelliğini çağırarak fiziksel görünümü çıkarın. Bu dizideki düğmeler, etkin rehberlik sırasında kullanılabilen düğmelerden farklıdır.
Etkin rehberlik bileşenleri
Adım adım rehberlik başladığında SDK, navigasyon kontrollerinin tamamını etkinleştirir. ActiveGuidanceUIState nesneniz aşağıdaki Google bileşenlerine erişim sağlar:
turnCard: Yaklaşan manevra talimatlarını, mesafe ölçümlerini ve şerit kılavuzunu gösteren birincil başlık banner'ı. Tanıdık bir gezinme hiyerarşisi oluşturmak ve özel görünümlerinizi bu hiyerarşiye göre sabitlemek için bu çubuğu düzeninizin üst kısmına yerleştirin.etaCard: Tahmini varış zamanını, kalan yolculuk süresini ve varış noktasına kalan mesafeyi gösteren altbilgi banner'ı. Ekranınızın alt kenarına yerleştirin veya koordinatlarını özel görev yönetimi sayfalarınıza entegre edin.tripProgressBar: Sürücüye mevcut rota boyunca ne kadar yol katettiğini gösteren dikey bir ilerleme çubuğu. Bunu harita kenarına sabitleyen eski düzenlerin aksine, istediğiniz yere (örneğin, özel kapsayıcınızın yan kenarı boyunca) sabitleme konusunda tamamen özgürsünüz.speedWidget: Hız göstergesi ve yayınlanan hız sınırları için kayan bir kontrol. API ayarlarınıza (GMSMapView'ninshouldDisplaySpeedometerveshouldDisplaySpeedLimitözellikleri) ve veri kullanılabilirliğine bağlı olarak bu görünüm, çalışma zamanında dört görsel durum (hiçbir şey göstermeme, yalnızca mevcut hız, yalnızca hız sınırı veya her iki değer) arasında dinamik olarak yeniden boyutlandırılır. Widget, önceden haber verilmeksizin bu boyutlar arasında geçiş yapabileceğinden, düzeninizin otomatik olarak uyum sağlayarak alan çakışmalarını önlemesi için her zaman kısıtlamaları kullanarak çevreleyen görünümleri sabitleyin.
buttons: Etkin kılavuz durumu için genişletilmiş bir kayan işlem görünümü dizisi (genellikle hem pusulayı hem de olay raporlama düğmesini içerir). Navigasyona hazır durumda olduğu gibi, diziyitype(.compassveya.reporting) ile filtreleyipviewözelliğini kullanarak fiziksel görünümü ayıklayarak bağımsız düğme görünümlerini bulabilir ve ayıklayabilirsiniz. Ardından bunları bağımsız olarak konumlandırabilir veya birGMSNavigationHidingStackViewekleyebilirsiniz. Bu yığın görünümü, düğmeleri dikey olarak düzenler ve yer kalmadığındaarrangedSubviewsdizisinin sonundan başlayarak düğmeleri gizler. Yönettiği görünümlerdeisHiddenayarını yapmayın.
NavigationStyleValues.headerNominalHeight değerine göre sabit bir yüksekliğe sahiptir. Bu yükseklik, tüm rota kartı içerikleri için yeterlidir. Kamera kadrajını sabit tutmak için örnek uygulamada gösterildiği gibi viewport öğesinin üst kısmını doğrudan turnCard öğesinin alt kenarına sabitleyin.
Dinamik istem bileşenleri
Olay uyarıları veya güvenlik kamerası uyarıları gibi istemler, aktif rehberlik sırasında bağımsız olarak tetiklenir.
Bir istem gösterilmeye hazır olduğunda SDK, temsilcinizin showPrompt(navMap:newPrompt:) geri çağırma işlevini çağırır ve doğrudan bir newPrompt görünümü iletir. Temsilciniz, bu istemi düzeninizin üzerine sorunsuz bir şekilde yerleştirmekten (genellikle harita kapsayıcınızın alt kenarına sabitlenir) sorumludur.
Gelen istem, ekranın alt kısmını kapladığından istemin görüntü alanıyla, Google logosuyla veya altta hizalanmış düğmelerle çakışmasını önlemek için düzeninizi güncellemeniz gerekir.
Temsilciniz, istemlerin ekrana nasıl girip çıkacağını da kontrol eder. Ayrıntılı bilgi için İstemleri özelleştirme başlıklı makaleyi inceleyin.
Ekran boyutunu ve geniş modu yönetme
Düzen temsilcisi, harita boyutlarındaki ve ekran yönündeki değişiklikleri işlemek için aşağıdaki özellikleri kullanır:
Yeniden boyutlandırmayı görüntüleme: Haritanın fiziksel boyutu her değiştiğinde düzeninizi ayarlar.
Geniş mod: Harita yeterince geniş olduğunda geniş formatlı bir düzen varyantına geçer.
Görünüm yeniden boyutlandırmaya yanıt verme
Bölünmüş ekran düzenleri, kapsayıcı kaydırıcıları ve cihaz döndürme genelliklesizeChanged(navMap:newState:) geri çağırma işlevini tetikler. Özel kullanıcı arayüzünüzde genel duyarlı ayarlamalar yapmak için bu geri çağırma işlevini kullanın. Özel öğeleri yeni bir ekran en boy oranına göre yeniden konumlandırmak, kendi özel genişlik veya yükseklik kesme noktalarınızı uygulamak ve yeniden boyutlandırma etkinliğinin geniş modu ne zaman açıp kapattığını algılayarak düzen varyantlarını güvenli bir şekilde değiştirmek için sizeChanged(navMap:newState:) öğesini uygulayın.
Geniş modu anlama
Geniş mod, harita kapsayıcısı kullanıcı arayüzü bileşenlerini yan yana gösterecek kadar geniş olduğunda etkinleşir.
Özel kullanıcı arayüzü öğelerinizi yeniden konumlandırmak ve haritanın ortasını sürücü için boş tutmak amacıyla durum nesnesinden wideMode boole değerini okuyun. Rota kartı ve TVS kartı gibi Google tarafından sağlanan bileşenler geniş modda otomatik olarak küçülüp yeniden şekillendiğinden, bu boole değerini okumak düzeninizin Google bileşenlerinin güncellendiği anda uyarlanmasını sağlar.
Düzen bileşenlerinizi standart ve geniş modlarda konumlandırmak için aşağıdaki örnekleri inceleyin:
Standart portre modu: Rota kartını ekranın üst kısmına, TVS kartını ise alt kısmına yerleştirin.
Geniş mod: Rota kartını ve TVS kartını ekranın ön kenarında tutun. Rota kartı üstte, TVS kartı ise altta olmalıdır. Kartlardan birini ön kenardan uzağa yerleştirmek, tanımlanmamış davranışlara neden olur. Görüntü alanını ve kendi içeriğinizi istediğiniz yere yerleştirebilirsiniz. Kartların son tarafı, yaygın bir seçimdir.
Durum geçişleri sırasında geniş mod kontrol ediliyor
Düzeniniz geniş mod varyasyonunu destekliyorsa yalnızca sizeChanged(navMap:newState:) değerine güvenmek yerine her durum geçişi geri çağırmasındaki wideMode boole değerini değerlendirin.
enableNavigationUI(navMap:newState:) ve startGuidance(navMap:newState:oldState:) gibi geri çağırmalar sırasında durum nesnesini kontrol edin. Bu yaklaşım, düzeninizin geniş mod durumunu değerlendirmesini ve yeni bir gezinme durumu başlar başlamaz doğru standart veya geniş düzen varyantını etkinleştirmesini sağlar.
Google stiline uyarlama
Özel görünümlerinizin Google tarafından sağlanan bileşenlerin aralığıyla eşleşmesine yardımcı olmak için SDK, NavigationStyleValues sınıfını sağlar. Sınıf özellikleri, ölçümleri
nokta cinsinden döndürür.
Örneğin, ekranın üst köşesinde rota kartının karşısına özel bir düğme yerleştirmek için düğmenin üst kenar boşluğu olarak NavigationStyleValues.headerTopPadding değerini kullanın. Düğme, rota kartının üst kenarıyla hizalanır.
Aşağıdaki ölçümler kullanılabilir:
NavigationStyleValues.buttonHorizontalPaddingNavigationStyleValues.buttonVerticalPaddingNavigationStyleValues.defaultCornerRadiusNavigationStyleValues.elementHorizontalPaddingNavigationStyleValues.headerFooterHorizontalPaddingNavigationStyleValues.headerNominalHeightNavigationStyleValues.headerTopPadding
Google tarafından sağlanan görünümlerde yerleşik kenar boşlukları da bulunur. Google tarafından sağlanan iki görünümü üst üste yerleştirdiğinizde, kenar boşluklarının toplanmaması için birini diğerinin layoutMarginsGuide ile sınırlandırın. Örneğin, hız widget'ını TVS kartının üstüne yerleştirmek için speedWidget öğesinin alt bağlantı noktasını etaCard.layoutMarginsGuide.topAnchor ile sınırlandırın.
İstemleri özelleştirme
Düzen temsilciniz, istemlerin ekrana nasıl girip çıkacağını ve hangi köşelerinin yuvarlatılacağını kontrol eder.
Animasyon istemleri
SDK, istemlere animasyon eklemez. Temsilciniz, her istemin nasıl görüneceğini ve kaybolacağını belirler:
showPrompt(navMap:newPrompt:)uygulamasında, istemi harita görünümüne başlangıç konumunda ekleyin ve ardından animasyonla yerine yerleştirin. Örneğin, istemi görünür alanın hemen altında veya üst kenarı sayfanın üst kısmıyla hizalanmış bir alt sayfanın arkasında başlatın.hidePrompt(navMap:oldPrompt:)içinde, istemi görünümden çıkaracak şekilde animasyon uygulayın ve animasyon bittiğinde istemi üst görünümünden kaldırın.changePrompt(navMap:newPrompt:oldPrompt:)içinde, bir istemi diğeriyle değiştirmek için gizleme ve gösterme animasyonlarınızı aynı anda çalıştırın.
Örnek uygulama bu yaklaşımı kullanır.
Trafik, daha iyi rotalar ve olaylarla ilgili istemleri devre dışı bırakmak için GMSNavigator öğesinin shouldDisplayPrompts özelliğini false olarak ayarlayın.
İstem köşeleri yuvarlatılıyor
İstemler NavigationRoundedCornerView protokolüne uygun olduğundan hangi köşelerinin yuvarlatılacağını seçebilirsiniz. Varsayılan olarak, istemin yalnızca üst köşeleri yuvarlatılır. Bu, ekranın alt kenarına sabitlenmiş bir istem için uygundur.
Alt sayfa gibi başka bir içeriğin üzerine istem yerleştirirseniz dört köşeyi de yuvarlayın:
newPrompt.roundedCorners = [
.layerMinXMinYCorner, .layerMaxXMinYCorner,
.layerMinXMaxYCorner, .layerMaxXMaxYCorner,
]
Örnek: Kısıtlamaya dayalı düzen uygulaması
Aşağıdaki örnekte; rota kartını, TVS kartını, düğmeleri, Google logosunu ve görüntü alanını konumlandırmak için otomatik düzen kısıtlamalarını kullanan temel bir düzen temsilcisi gösterilmektedir. Ayrıca istemleri ekrana kaydırarak açıp kapatır. Örneği kısa tutmak için geniş mod, hız widget'ı veya gezi ilerleme çubuğu kullanılmamıştır. Bu nedenle, yalnızca dikey yönü destekleyen uygulamalar için uygundur.
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)
}
}
Temsilciyi kullanmak için Temel bilgiler kontrol listesinde açıklandığı gibi, navigasyonu etkinleştirmeden önce harita görünümünüzde ayarlayın.
Sınırlamalar ve bilinen sorunlar
Düzen özelleştirme, sabit düzen şablonları kullanan CarPlay için geçerli değildir. CarPlay ekranı için oluşturduğunuz bir harita görünümünde setLayoutDelegate(_:) yöntemini çağırırsanız yöntem bir hata verir.
Belirli SDK sürümlerini etkileyen sorunlar için sürüm notlarındaki Bilinen sorunlar bölümüne bakın.