iOS Swift CarPlay Open Maps By URL Scheme
9 min readJul 17, 2023
OS: 氣到想寫文章(?
需求:
- 由CPPointOfInterestTemplate顯示地點
- 用CPInformationTemplate顯示地點細節
- CPInformationTemplate下方按鈕顯示”導航”二字
- 點擊導航後依照是否安裝Google Maps,顯示Apple Maps跟Google Maps的選單給使用者決定要開啟的target
Press enter or click to view image in full size![]()
結論就是:
- 前面都沒什麼問題,就堆堆積木而已
- 但選單很有障礙,如果用CPAlertTemplate給使用者選項,疑似會影響到templateApplicationScene.didConnect儲存起來的CPTemplateApplicationScene,導致Maps程式會開啟在使用者的iPhone,變得跟以UIApplication.shared.open開啟一樣的效果
- 所以要用CPActionSheetTemplate去顯示選單,這樣就可以在CarPlay開啟地圖程式了
Get ccc’s stories in your inbox
Join Medium for free to get updates from this writer.
以下為程式碼(雖然省略部分但應該可以看懂吧?):
CarPlaySceneDelegate.swift
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController) {
self.coordinator = CPTabBarCoordinator(interfaceController, scene: templateApplicationScene)
self.coordinator?.start()
}CPTabBarCoordinator.swift
private var mapVC: CPMapVC? = nil
// 控制template跳轉
private var interfaceController: CPInterfaceController?
private var cpScene: CPTemplateApplicationScene?
required init(_ interfaceController: CPInterfaceController, scene: CPTemplateApplicationScene) {
self.interfaceController = interfaceController
self.cpScene = scene
}
func start() {
// 建立主要的Map
self.mapVC = CPMapVC()
self.mapVC?.mapDataInit()
var templates: [CPTemplate] = []
switch option {
case .home:
let poi = CPTemplateManager.createPOITemplate() { [weak self] in
self?.interfaceController?.popTemplate(animated: true)
}
poi.setPointsOfInterest(self.buildPlaces(), selectedIndex: NSNotFound)
template = poi
case .favorite:
let poi = CPTemplateManager.createFavoriteTemplate() { [weak self] in
self?.interfaceController?.popTemplate(animated: true)
}
poi.setPointsOfInterest(self.buildPlaces(), selectedIndex: NSNotFound)
template = poi
case .filter:
template = CPTemplateManager.createFilterTemplate() { [weak self] option in
switch option {
......
}
let template = CPTemplateManager.createPOITemplate() { [weak self] in
self?.interfaceController?.popTemplate(animated: true)
}
template.setPointsOfInterest(self?.buildPlaces() ?? [], selectedIndex: NSNotFound)
self?.interfaceController?.pushTemplate(template, animated: true)
} backAction: {
// tabBar時應該不會需要返回
}
}
let barTemplate = CPTabBarTemplate(templates: templates)
barTemplate.delegate = self
self.interfaceController?.setRootTemplate(barTemplate, animated: true)
}
private func buildPlaces() -> [CPPointOfInterest] {
guard let displayList = self.mapVC?.getDisplayList() else {
return []
}
return displayList.compactMap() { [weak self] name -> CPPointOfInterest? in
guard let item = self?.mapVC?.getLotDetail(name: name) else {
return nil
}
let location = MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: item.lat, longitude: item.lng)))
let place = CPPointOfInterest(
location: location,
title: item.name,
subtitle: item.phone,
summary: item.address,
detailTitle: item.name,
detailSubtitle: item.phone,
detailSummary: item.address,
pinImage: UIImage(systemName: "car"))
// 設定btn
let button = CPTextButton(title: AppString.detailString, textStyle: .normal) { button in
let infoTemplate = CPTemplateManager.createDetailTemplate(item: item) { [weak self] in
// 先回本頁再選擇
self?.interfaceController?.popTemplate(animated: true) { [weak self] _, _ in
let alert = CPTemplateManager.createMapSelectTemplate(item: item) { [weak self] url in
self?.cpScene?.open(url, options: nil) { [weak self] _ in
self?.interfaceController?.dismissTemplate(animated: true)
}
}
self?.interfaceController?.presentTemplate(alert, animated: true)
}
}
self?.interfaceController?.pushTemplate(infoTemplate, animated: true)
}
place.primaryButton = button
return place
}
}CPTemplateManager.swift
static func createMapSelectTemplate(item: ChargingItem, openURLAction: @escaping (URL) -> Void) -> CPActionSheetTemplate {
let title = AppString.plsChooseMaps
var msg: String? = nil
var actions = [
CPAlertAction(
title: AppString.appleMaps,
style: .default,
handler: { _ in
let urlString = "http://maps.apple.com/?daddr=\(item.lat),\(item.lng)&dirflg=d"
if let url = URL(string: urlString) {
openURLAction(url)
}
}
)
]
if UIApplication.shared.canOpenURL(URL(string:"comgooglemaps://")!) {
actions.append(CPAlertAction(
title: AppString.googleMaps,
style: .default,
handler: { _ in
let urlString = "comgooglemaps://?daddr=\(item.lat),\(item.lng)&directionsmode=driving"
if let url = URL(string: urlString) {
openURLAction(url)
}
}
))
} else {
msg = "未安裝Google Maps"
}
return CPActionSheetTemplate(
title: title,
message: msg,
actions: actions
)
}murmur:
- 等CarPlay entitlements我等了2個月才下來
- 因為App本身沒有導航功能,說服不了Apple給我maps template的權限,他只給了我charging的,不然本來我用maps template設計的版面更好看更靈活,後來只好又花點時間改成charging的templates設計
- 各種templates的.backButton設定好像有時候有用有時候沒用,非常困擾






