12. CarPlay导航应用开发:CPMapTemplate、路线规划、导航指令显示
说实话,CarPlay导航开发这块,我当年刚接触的时候也踩过不少坑。特别是 CPMapTemplate 这个核心类,看着文档挺简单,真上手才发现细节特别多。今天咱们就把它彻底讲透。
12.1 CPMapTemplate 是什么?
CPMapTemplate 是 CarPlay 导航应用的画布。你想想看,用户开车时看到的那个地图界面,本质上就是这个模板在管理。它负责显示地图、路线、导航指令,还有底部的操作栏。
我个人习惯把 CPMapTemplate 理解成「导航的舞台」。所有跟导航相关的 UI 元素,都得在这个舞台上布置。它不像 CPListTemplate 那样简单,因为导航场景下,用户的操作是动态的、实时的。
12.2 创建 CPMapTemplate 实例
创建其实不难,但有几个参数你得注意。看代码:
// 创建地图模板
let mapTemplate = CPMapTemplate()
// 设置导航会话的代理
mapTemplate.mapDelegate = self
// 配置底栏按钮
let startButton = CPBarButton(type: .text) { button in
// 开始导航的逻辑
}
startButton.title = "开始导航"
mapTemplate.leadingNavigationBarButtons = [startButton]
// 设置地图的可见区域
let center = CLLocationCoordinate2D(latitude: 39.9042, longitude: 116.4074)
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let region = MKCoordinateRegion(center: center, span: span)
mapTemplate.setVisibleRegion(region, animated: false)
嗯,这里要注意:mapDelegate 必须实现 CPMapTemplateDelegate 协议。我在项目中遇到过,有人忘了设置代理,结果导航指令死活不显示。排查了半天才发现是代理没连上。
12.3 路线规划:从 A 到 B 的核心逻辑
路线规划说白了就是算路。CarPlay 本身不负责算路,它只负责展示。真正的路线计算,你得用 MKDirections 或者自己的后端服务。
我建议的做法是:
- 用户输入目的地后,先调自己的路线引擎算路
- 把算好的路线封装成
CPRoute对象 - 通过
startNavigationSession启动导航会话
看一个完整的例子:
// 1. 计算路线
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: startCoord))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: endCoord))
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { response, error in
guard let route = response?.routes.first else { return }
// 2. 创建 CPRoute
let cpRoute = CPRoute(
route: route,
routeChoice: CPRouteChoice(
summaryVariants: ["推荐路线", "约30分钟"],
additionalInformationVariants: nil,
selectionSummaryVariants: nil
)
)
// 3. 启动导航会话
let session = mapTemplate.startNavigationSession(for: cpRoute)
session.upcomingManeuvers = self.generateManeuvers(from: route)
}
CPRouteChoice 支持多条路线选择,用户可以通过旋钮或触摸切换。我做过一个项目,只给了一条路线,结果用户反馈说「我想走高速,你偏给我走小路」。
12.4 导航指令显示:让用户知道怎么走
导航指令是 CarPlay 导航体验的关键。你想想看,用户开车时不可能盯着地图看,他们需要的是清晰的语音和文字提示。
CPManeuver 就是用来描述每个导航动作的。它包含:
- maneuverType:动作类型(左转、右转、环岛等)
- junctionType:路口类型
- instructionVariants:指令文本(支持多语言)
- initialHeading:初始方向
- finalHeading:最终方向
我曾经犯过一个错误:只设置了 instructionVariants,没设置 maneuverType。结果 CarPlay 显示了一个空箭头,用户完全看不懂。后来我养成了习惯,每个 CPManeuver 都完整配置。
// 创建导航指令
let maneuver = CPManeuver()
maneuver.maneuverType = .turnLeft
maneuver.instructionVariants = ["前方500米左转", "500米后左转"]
maneuver.initialHeading = 90
maneuver.finalHeading = 180
// 设置到导航会话
session.upcomingManeuvers = [maneuver]
// 更新当前指令
session.currentManeuver = maneuver
instructionVariants 数组的顺序很重要。CarPlay 会根据当前语言环境自动选择最合适的指令。如果你只提供一个中文指令,那英文用户就看不到任何文字。我建议至少提供中英文两种。
12.5 导航会话的生命周期管理
导航会话不是永久的。用户可能中途取消导航,或者到达目的地后自动结束。你需要监听这些事件:
| 事件 | 回调方法 | 说明 |
|---|---|---|
| 导航开始 | mapTemplateDidBeginNavigation |
会话已启动,可以开始更新指令 |
| 导航结束 | mapTemplateDidEndNavigation |
用户主动取消或到达目的地 |
| 路线更新 | mapTemplate:didUpdateNavigationSession: |
路线重新规划时触发 |
| 指令变更 | mapTemplate:didUpdateManeuver: |
当前导航指令发生变化 |
我个人习惯在 mapTemplateDidEndNavigation 里做一些清理工作,比如停止位置更新、释放资源。不然的话,导航结束了还在后台跑定位,用户电量会掉得很快。
12.6 知识体系总览
下面这张图是我自己整理的,把 CarPlay 导航开发的核心脉络画出来了。你照着这个思路走,基本不会迷路。
12.7 避坑指南
最后,分享几个我实际项目中踩过的坑:
- 地图模板只能有一个:CarPlay 不允许同时存在多个
CPMapTemplate。如果你 push 了新的地图模板,旧的会被自动销毁。我之前想搞个「分屏导航」,结果发现根本行不通。 - 导航指令要提前生成:别等到用户快到路口了才生成指令。CarPlay 需要提前缓存,否则会显示空白。我建议在导航开始时,就把整条路线的所有指令都生成好。
- 语音提示要配合:
CPManeuver只负责显示,语音提示你得自己用AVSpeechSynthesizer播报。CarPlay 不会自动帮你读指令。 - 测试时用模拟器:真机调试 CarPlay 导航很麻烦。我一般先用模拟器跑通逻辑,再上真机验证。模拟器里可以用
CLLocation模拟位置变化。
CPManeuver 的 userInfo 里塞一些自定义数据,比如「这个路口有摄像头」之类的。虽然 CarPlay 不会显示,但你可以自己在语音提示里用。
好了,这一章的内容就到这。CarPlay 导航开发的核心就是 CPMapTemplate + 路线规划 + 指令显示,三者缺一不可。你只要把这三个环节打通,剩下的就是细节打磨了。