基于 CarPlay 框架开发支持 CarPlay 的车机应用。适用于为车载屏幕构建导航、音频、通讯、电动车充电、停车或快捷点餐类应用,使用 CPTemplateApplicationScene、CPInterfaceController 模板层级结构、CPListTemplate、CPMapTemplate、CPNowPlayingTemplate,配置 CarPlay 专项权限(Entitlements),以及整合 CarPlay 模拟器进行测试等场景。
CarPlay
构建基于模板且具备对应类别权限的车载应用(CarPlay)。
适用范围:Swift 6.3,iOS 26+。
参阅 references/carplay-patterns.md 了解扩展模式,包括完整的导航会话、仪表盘 Scene 以及高级模板组合。
适用边界:完整的 CarPlay 框架应用需要使用类别权限(Category Entitlements)、CPTemplateApplicationScene、CPTemplateApplicationSceneDelegate、CPInterfaceController 和系统级 CPTemplate 导航。可显示在 CarPlay 上的 WidgetKit 小组件和 ActivityKit 实时活动(Live Activities)属于独立的系统体验;其实现应路由至对应领域,在此处仅保留 CarPlay 特有的校验逻辑。
目录
权限与项目配置
前往 Apple 的 CarPlay 权限申请表单 申请特定类别的权限,接受补充协议后,配置以下已获批的类别 Key。
各类别的权限 Key
| 权限 Key | 适用类别 |
|---|---|
com.apple.developer.carplay-audio |
音频 |
com.apple.developer.carplay-communication |
通讯 |
com.apple.developer.carplay-maps |
导航 |
com.apple.developer.carplay-charging |
电动车充电 |
com.apple.developer.carplay-parking |
停车 |
com.apple.developer.carplay-quick-ordering |
快捷点餐 |
项目配置步骤
- 在开发者中心(Developer Portal)的 Additional Capabilities 下更新 App ID。
- 为更新后的 App ID 生成全新的 Provisioning Profile(描述文件)。
- 在 Xcode 中禁用自动签名(Automatic Signing),并导入 CarPlay 描述文件。
- 添加
Entitlements.plist并将相应的权限 Key 设置为true。 - 将 Code Signing Entitlements 构建设置指定为
Entitlements.plist的路径。
核心类型说明
| 类型 | 职责/作用 |
|---|---|
CPTemplateApplicationScene |
用于 CarPlay 屏幕显示的 UIScene 子类 |
CPTemplateApplicationSceneDelegate |
管理 Scene 的连接与断开生命周期 |
CPInterfaceController |
系统提供的控制器,用于设置根模板以及 push、present 或 pop 模板 |
CPTemplate |
所有 CarPlay 模板的抽象基类 |
CPSessionConfiguration |
车载屏幕限制与内容样式配置 |
Scene 配置
在 Info.plist 中声明 CarPlay Scene,并实现 CPTemplateApplicationSceneDelegate 以响应 CarPlay 连接事件。
Info.plist Scene Manifest
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationScene</string>
<key>UISceneConfigurationName</key>
<string>CarPlaySceneConfiguration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>
</dict>
</array>
</dict>
</dict>
Scene Delegate(非导航类)
非导航类应用仅接收接口控制器(Interface Controller),不提供 Window 实例。
import CarPlay
final class CarPlaySceneDelegate: UIResponder,
CPTemplateApplicationSceneDelegate {
var interfaceController: CPInterfaceController?
func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController
) {
self.interfaceController = interfaceController
interfaceController.setRootTemplate(buildRootTemplate(),
animated: true, completion: nil)
}
func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didDisconnectInterfaceController interfaceController: CPInterfaceController
) {
self.interfaceController = nil
}
}
Scene Delegate(导航类)
导航类应用会同时接收接口控制器和 CPWindow 实例。
通过设置 Window 的根视图控制器(rootViewController)来绘制地图内容。
func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController,
to window: CPWindow
) {
self.interfaceController = interfaceController
self.carWindow = window
window.rootViewController = MapViewController()
let mapTemplate = CPMapTemplate()
mapTemplate.mapDelegate = self
interfaceController.setRootTemplate(mapTemplate, animated: true,
completion: nil)
}
模板概述
CarPlay 提供了一套固定的模板类型。应用负责提供数据内容,系统负责在车载屏幕上进行渲染。
通用模板
| 模板 | 用途 |
|---|---|
CPTabBarTemplate |
带有标签页(Tab)子模板的容器 |
CPListTemplate |
可滚动的分组列表 |
CPGridTemplate |
可点击图标按钮构成的网格(最多 8 个) |
CPInformationTemplate |
键值信息展示,最多支持 3 个操作按钮 |
CPAlertTemplate |
模态弹窗警告,最多支持 2 个操作按钮 |
CPActionSheetTemplate |
模态操作表单 |
类别专属模板
| 模板 | 适用类别 |
|---|---|
CPMapTemplate |
导航 -- 带导航栏的地图叠加层 |
CPSearchTemplate |
导航 -- 目的地搜索 |
CPNowPlayingTemplate |
音频 -- 共享的“正在播放”界面 |
CPPointOfInterestTemplate |
电动车充电 / 停车 / 点餐 -- POI 地图 |
CPContactTemplate |
通讯 -- 联系人卡片 |
导航层级结构
使用 pushTemplate(_:animated:completion:) 将模板推入栈中。
使用 presentTemplate(_:animated:completion:) 进行模态展示。
使用 popTemplate(animated:completion:) 返回上一级。
CPTabBarTemplate 必须设为根模板(Root Template)-- 不能被 push 或 present。
CPTabBarTemplate
let browseTab = CPListTemplate(title: "Browse",
sections: [CPListSection(items: listItems)])
browseTab.tabImage = UIImage(systemName: "list.bullet")
let tabBar = CPTabBarTemplate(templates: [browseTab, settingsTab])
tabBar.delegate = self
interfaceController.setRootTemplate(tabBar, animated: true, completion: nil)
CPListTemplate
let item = CPListItem(text: "Favorites", detailText: "12 items")
item.handler = { selectedItem, completion in
self.interfaceController?.pushTemplate(detailTemplate, animated: true,
completion: nil)
completion()
}
let section = CPListSection(items: [item], header: "Library",
sectionIndexTitle: nil)
let listTemplate = CPListTemplate(title: "My App", sections: [section])
导航类应用
导航类应用使用 com.apple.developer.carplay-maps 权限。它们是唯一能接收到用于绘制地图内容的 CPWindow 的应用类别。根模板必须是 CPMapTemplate。
行程预览与路线选择
let routeChoice = CPRouteChoice(
summaryVariants: ["Fastest Route", "Fast"],
additionalInformationVariants: ["Via Highway 101"],
selectionSummaryVariants: ["25 min"]
)
let trip = CPTrip(origin: origin, destination: destination,
routeChoices: [routeChoice])
mapTemplate.showTripPreviews([trip], textConfiguration: nil)
开启导航会话
extension CarPlaySceneDelegate: CPMapTemplateDelegate {
func mapTemplate(_ mapTemplate: CPMapTemplate,
startedTrip trip: CPTrip,
using routeChoice: CPRouteChoice) {
let session = mapTemplate.startNavigationSession(for: trip)
session.pauseTrip(for: .loading, description: "Calculating route...")
let maneuver = CPManeuver()
maneuver.instructionVariants = ["Turn right onto Main St"]
maneuver.symbolImage = UIImage(systemName: "arrow.turn.up.right")
session.upcomingManeuvers = [maneuver]
let estimates = CPTravelEstimates(
distanceRemaining: Measurement(value: 5.2, unit: .miles),
timeRemaining: 900)
session.updateEstimates(estimates, for: maneuver)
}
}
地图按钮
let zoomIn = CPMapButton { _ in self.mapViewController.zoomIn() }
zoomIn.image = UIImage(systemName: "plus.magnifyingglass")
mapTemplate.mapButtons = [zoomIn, zoomOut]
CPSearchTemplate
extension CarPlaySceneDelegate: CPSearchTemplateDelegate {
func searchTemplate(_ searchTemplate: CPSearchTemplate,
updatedSearchText searchText: String,
completionHandler: @escaping ([CPListItem]) -> Void) {
performSearch(query: searchText) { results in
completionHandler(results.map {
CPListItem(text: $0.name, detailText: $0.address)
})
}
}
func searchTemplate(_ searchTemplate: CPSearchTemplate,
selectedResult item: CPListItem,
completionHandler: @escaping () -> Void) {
// 导航至选中的目的地
completionHandler()
}
}
音频类应用
音频类应用使用 com.apple.developer.carplay-audio 权限。它们在列表中展示可浏览的内容,并使用 CPNowPlayingTemplate 进行播放控制。获得音频权限的应用无法使用 CPInformationTemplate。
正在播放模板(Now Playing Template)
CPNowPlayingTemplate 是一个共享单例,它从 MPNowPlayingInfoCenter 读取元数据。切勿手动实例化新对象。
let nowPlaying = CPNowPlayingTemplate.shared
nowPlaying.isUpNextButtonEnabled = true
nowPlaying.isAlbumArtistButtonEnabled = true
nowPlaying.updateNowPlayingButtons([
CPNowPlayingShuffleButton { _ in self.toggleShuffle() },
CPNowPlayingRepeatButton { _ in self.toggleRepeat() }
])
nowPlaying.add(self) // 注册为 CPNowPlayingTemplateObserver
Siri 语音助手 Cell
支持 INPlayMediaIntent 的音频类应用可以展示 Siri 助手单元格(Assistant Cell)。通讯类应用则配合 .startCall 使用 INStartCallIntent。
let config = CPAssistantCellConfiguration(
position: .top, visibility: .always, assistantAction: .playMedia)
let listTemplate = CPListTemplate(
title: "Playlists",
sections: [CPListSection(items: items)],
assistantCellConfiguration: config)
通讯类应用
通讯类应用使用 com.apple.developer.carplay-communication 权限。它们展示消息列表和联系人,并支持使用 INStartCallIntent 进行 Siri 发起的通话。
CPMessageListItem 没有应用自定义的选择回调句柄(selection handler)。选中时,CarPlay 会根据该项的电话/邮箱、未读状态或现有会话配置,自动调用 Siri 执行撰写、读取或回复等行为。
let leading = CPMessageListItemLeadingConfiguration(
leadingItem: .star, leadingImage: nil, unread: true)
let trailing = CPMessageListItemTrailingConfiguration(
trailingItem: .none, trailingImage: nil)
let message = CPMessageListItem(
conversationIdentifier: "conv-123",
text: "Jane",
leadingConfiguration: leading,
trailingConfig




