Highlight
iOS 14 将 CarPlay framework 的模板从导航扩展到音频、通信、EV 充电、停车和快餐点餐应用,开发者可以用 UIScene、预定义模板和单一类别 entitlement 把车内任务做成几秒内完成的车屏体验。
核心内容
CarPlay 以前的第三方框架能力主要服务导航 App。音频 App 依赖 playable content API,通信 App 依赖 SiriKit 和 CallKit,停车、充电、快餐点餐这类和开车直接相关的任务还不能用同一套 CarPlay 模板放到车屏上。结果是,很多本该在车里完成的操作仍然回到手机,驾驶场景不适合这样做。
iOS 14 的变化是给这些类别开放新的 CarPlay templates。Apple 把问题限定得很窄:CarPlay 是给驾驶员用的,界面要围绕最常见的车内任务,交互要短,信息要能扫一眼完成判断。账号设置、完整菜单、复杂管理页都应该留在 iPhone 上。
对开发者来说,这不是把 iPhone App 搬到车机屏幕。CarPlay App 运行在 iPhone 上,通过 CarPlay framework 把预定义模板显示到车屏。App 需要采用 UIScene,声明 CarPlay scene,在连接车屏时拿到 CPInterfaceController,再把列表、标签栏、Now Playing、POI、信息页这些模板组织成安全的车内流程。
Session 后半段把新类别落到具体场景。快餐点餐 App 可以展示附近门店、历史订单、收藏和订单信息;EV 充电和停车 App 可以用地图找地点、检查可用性,并继续进入选择、导航或摘要页面。最后还提醒开发者,CarPlay 需要申请 entitlement,而且 App 必须选择单一类别。
详细内容
CarPlay scene 从 Info.plist 开始
(04:24)使用 CarPlay framework 的前提是采用 UIScene。App 要在 UIApplicationSceneManifest 里额外声明一个 CarPlay template application scene,并指定对应的 scene delegate。这个配置和 iPhone scene 并存,用户从 CarPlay 主屏幕启动 App 时,系统连接的是车屏 scene。
// CarPlay Scene Manifest
<key>UIApplicationSceneManifest</key>
<dict>
<key>UISceneConfigurations</key>
<dict>
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationScene</string>
<key>UISceneConfigurationName</key>
<string>MyApp—Car</string>
<key>UISceneDelegateClassName</key>
<string>MyApp.CarPlaySceneDelegate</string>
</dict>
</array>
</dict>
</dict>
关键点:
UIApplicationSceneManifest是 UIScene 的入口,CarPlay 配置放在这里。CPTemplateApplicationSceneSessionRoleApplication声明这是 CarPlay 模板应用 scene。CPTemplateApplicationScene让系统为车屏创建 CarPlay scene。MyApp.CarPlaySceneDelegate是后续接收 CarPlay 连接和断开的 delegate。
连接车屏后设置根模板
(05:12)CarPlay scene delegate 的 didConnect 会在 App 启动到车屏时调用。开发者需要保存 CPInterfaceController,因为之后 push、set root、更新模板都靠它完成。示例里先构造一个列表,再把它设置成根模板。
// CarPlay App Lifecycle
import CarPlay
class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
var interfaceController: CPInterfaceController?
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController) {
self.interfaceController = interfaceController
let item = CPListItem(text: "Rubber Soul", detailText: "The Beatles")
let section = CPListSection(items: [item])
let listTemplate = CPListTemplate(title: "Albums", sections: [section])
interfaceController.setRootTemplate(listTemplate, animated: true)
}
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
didDisconnect interfaceController: CPInterfaceController) {
self.interfaceController = nil
}
关键点:
CPTemplateApplicationSceneDelegate是 CarPlay scene 生命周期的入口。didConnect interfaceController表示车屏已经连接,可以开始显示模板。CPListItem、CPListSection、CPListTemplate组合出一个列表界面。setRootTemplate把模板放到车屏栈的根部。didDisconnect释放interfaceController,避免继续操作已经断开的车屏。
音频 App 从 playable content 迁移到模板
(05:54)iOS 14 里 playable content 会被废弃,但旧系统仍可继续使用它。新的音频模板让 App 自己构造列表、标签栏和 Now Playing 体验,iOS 13 及更早版本可以继续走 playable content。
// CPListTemplate
import CarPlay
let item = CPListItem(text: "Rubber Soul", detailText: "The Beatles")
let section = CPListSection(items: [item])
let listTemplate = CPListTemplate(title: "Albums", sections: [section])
self.interfaceController.pushTemplate(listTemplate, animated: true)
关键点:
CPListItem对应列表里的单行内容,适合专辑、播放列表、消息线程这类对象。CPListSection把多个 item 组织到同一个列表区块。CPListTemplate是 CarPlay 最基础的模板之一。pushTemplate用于从当前页面进入下一层模板。
(06:09)列表项被选中时,CarPlay 会调用 listItemHandler。handler 里可以先启动播放或配置下一个模板,再调用 completion()。如果异步工作没有立刻完成,CarPlay 会在列表项上显示 spinner,直到 completion 被调用。
// CPListTemplate
import CarPlay
let item = CPListItem(text: "Rubber Soul", detailText: "The Beatles")
item.listItemHandler = { item, completion, [weak self] in
// Start playback, then...
self?.interfaceController.pushTemplate(CPNowPlayingTemplate.shared, animated: true)
completion()
}
// Later...
item.image = ...
关键点:
listItemHandler是用户点选列表项后的入口。- handler 接收被选中的
item和必须调用的completion。 - 示例在启动播放后 push 到共享的
CPNowPlayingTemplate。 completion()通知 CarPlay 当前选择处理完成,可以移除 busy 状态。item.image = ...对应 iOS 14 新增的动态列表项更新能力。
(07:58)CPTabBarTemplate 是 iOS 14 新增的容器模板。它把多个模板变成 CarPlay 用户熟悉的标签栏界面,并允许动态增加、移除、重排标签,或更新某个标签上的 badge。
// CPTabBarTemplate
import CarPlay
let item = CPListItem(text: "Rubber Soul", detailText: "The Beatles")
let section = CPListSection(items: [item])
let favorites = CPListTemplate(title: "Albums", sections: [section])
favorites.tabSystemItem = .favorites
favorites.showsTabBadge = true
let albums: CPGridTemplate = ...
albums.tabTitle = "Albums"
albums.tabImage = ...
let tabBarTemplate = CPTabBarTemplate(templates: [favorites, albums])
self.interfaceController.setRootTemplate(tabBarTemplate, animated: false)
// Later...
favorites.showsTabBadge = false
tabBarTemplate.updateTemplates([favorites, albums])
关键点:
favorites和albums是标签栏里的两个模板。tabSystemItem使用系统提供的标签栏图标。showsTabBadge可以在模板标签上显示状态提示。CPTabBarTemplate(templates:)把多个模板装进同一个容器。updateTemplates支持运行时更新标签栏内容和 badge 状态。
(09:34)音频列表在 iOS 14 获得更多展示能力。CPListImageRowItem 可以在一行里展示一组封面图,每张图都能独立响应选择。Apple Books 用它在 CarPlay 上展示最近的有声书。
// List Items for Audio Apps
import CarPlay
let gridImages: [UIImage] = ...
let imageRowItem = CPListImageRowItem(text: "Recent Audiobooks", images: gridImages)
imageRowItem.listItemHandler = { item, completion in
print("Selected image row header!")
completion()
}
imageRowItem.listImageRowHandler = { item, index, completion in
print("Selected artwork at index \(index)!")
completion()
}
let section = CPListSection(items: [imageRowItem])
let listTemplate = CPListTemplate(title: "Listen Now", sections: [section])
self.interfaceController.pushTemplate(listTemplate, animated: true)
关键点:
CPListImageRowItem用一组图片表达一组可选内容。listItemHandler处理整行标题被选中的情况。listImageRowHandler处理某一张封面被选中的情况,index指出具体位置。- 两个 handler 都要在异步工作结束后调用
completion()。
(12:50)Now Playing 是共享模板。系统可能在用户点 CarPlay 主屏幕的 Now Playing 按钮时直接启动 App 并展示它,所以音频 App 应该在 CarPlay scene 连接后尽快配置 CPNowPlayingTemplate.shared。
// Now Playing Template
import CarPlay
class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController) {
let nowPlayingTemplate = CPNowPlayingTemplate.shared
let rateButton = CPNowPlayingPlaybackRateButton() { button in
// Change the playback rate!
}
nowPlayingTemplate.updateNowPlayingButtons([rateButton])
}
}
关键点:
CPNowPlayingTemplate.shared表示 App 内只有一个 Now Playing 模板实例。- 初始化应尽早完成,因为系统可以替 App 展示这个模板。
CPNowPlayingPlaybackRateButton给 Now Playing 底部增加播放速度按钮。updateNowPlayingButtons更新 Now Playing 可见按钮。
通信 App 继续依赖 SiriKit 和 CallKit
(14:07)通信 App 在 iOS 14 也能使用 CarPlay framework 显示联系人、消息列表和消息状态,但语音和电话能力仍然要通过 SiriKit 与 CallKit 提供。新的 CPMessageListItem 用于消息线程列表,用户点按后,Siri 会根据 item 参数自动进入撰写、朗读或回复流程,而不是调用普通列表 handler。
(16:12)通信类别还新增了 contact template。它可以显示联系人图片、最多三行描述文字、最多四个动作按钮和导航栏按钮。这个模板把地址簿里的核心操作放到车屏上,但仍然要求流程短,避免把完整通讯录管理搬进 CarPlay。
POI 模板服务停车、充电和快餐点餐
(17:52)EV 充电、停车和快餐点餐的第一个任务通常是选地点。iOS 14 新增的 CPPointOfInterestTemplate 结合 MapKit 地图和信息面板,App 提供最多 12 个 CPPointOfInterest,CarPlay 负责地图显示、平移缩放和靠近点位的自动聚合。
(19:46)当地图区域变化时,App 应该重新计算附近地点,并把新结果交给模板。这样用户移动、平移或缩放地图时,车屏上的地点列表会跟着当前区域更新。
// CPPointOfInterestTemplateDelegate
func pointOfInterestTemplate(_ template: CPPointOfInterestTemplate,
didChangeMapRegion region: MKCoordinateRegion) {
self.locationManager.locations(for: region) { locations in
template.setPointsOfInterest(locations, selectedIndex: 0)
}
}
关键点:
CPPointOfInterestTemplateDelegate接收地图区域变化。didChangeMapRegion给出新的MKCoordinateRegion。- App 可以查询自己的地点库,也可以结合 MapKit 查找附近目标。
setPointsOfInterest用新地点替换模板上的列表和地图点位。
(20:23)每个地点都要转成 CPPointOfInterest。Session 示例把应用自己的模型映射成标题、副标题、说明文字和图片,并尽量复用已有模型,减少模板更新成本。
// CPPointOfInterest creation
func locations(for region: MKCoordinateRegion,
handler: ([CPPointOfInterest]) -> Void) {
var tempateLocations: [CPPointOfInterest] = []
for clientModel in self.executeQuery(for: region) {
let templateModel : CPPointOfInterest = self.locations[clientModel.mapItem] ??
CPPointOfInterest(location: clientModel.mapItem,
title: clientModel.title,
subtitle: clientModel.subtitle,
informativeText: clientModel.informativeText,
image: clientModel.mapImage)
tempateLocations.append(templateModel)
}
handler(templateLocations)
}
关键点:
locations(for region:)按当前地图区域生成 POI 列表。executeQuery(for:)代表 App 自己的附近地点查询。CPPointOfInterest汇总地图位置、标题、副标题、说明和图片。- 示例复用
self.locations[clientModel.mapItem],避免每次刷新都重建所有模型。
(21:05)地点被选中后,信息面板可以显示最多两个按钮。示例里 CPPointOfInterestButton 用来选择某个地点,并更新图钉图片和 selectedIndex,让车屏立刻反映新的选中状态。
// Point of Interest Template location selection
let primaryButton = CPPointOfInterestButton(title: "Select") { button, [weak self] in
let selectedIndex = ...
if selectedIndex != NSNotFound {
// Remove any existing selected state on previous location
self?.selectedLocation.image = defaultMapImage
// Change annotation for selected POI
self?.selectedLocation = templateModel
templateModel.image = selectedMapImage
// Update the template with new values
self?.pointOfInterestTemplate.selectedIndex = selectedIndex
}
}
let templateModel: CPPointOfInterest = ...
templateModel.primaryButton = primaryButton
关键点:
CPPointOfInterestButton把地点卡片上的动作绑定到 handler。selectedIndex是当前选中地点的位置。- 更新
image可以改变地图标注的视觉状态。 - 设置
pointOfInterestTemplate.selectedIndex会把选择状态同步到车屏。
信息模板完成订单、停车或充电摘要
(22:29)CPInformationTemplate 用于展示文本并接收用户响应。它支持一列或两列标签,以及 footer buttons。快餐点餐 App 可以用它显示订单摘要或确认页,EV 充电 App 可以用它展示充电站的重要信息。
(24:38)发布前还要处理 entitlement。CarPlay App 必须是单一类别,所选 entitlement 会决定 App 能使用哪些 CarPlay templates。音频 App 如果还要支持 iOS 13 或更早版本,可以让 playable content 和 iOS 14 的音频模板共存。
核心启发
车载音频首页
做什么:把音乐、有声书或播客 App 的 CarPlay 首页改成标签栏,包含收藏、专辑、最近播放和 Now Playing。
为什么值得做:iOS 14 的音频模板让 App 从 playable content 的系统生成树状界面,转向自己组织列表、图片行和共享 Now Playing。
怎么开始:采用 UIScene,声明 CarPlay scene,用 CPListTemplate 做列表,用 CPListImageRowItem 展示封面,用 CPNowPlayingTemplate.shared 在连接车屏时配置播放按钮。
停车场查找和选择
做什么:在 CarPlay 上显示附近停车场,让驾驶员看价格、距离或可用信息,然后选择一个地点继续导航。
为什么值得做:Session 明确把停车列为 iOS 14 首次支持的 CarPlay 类别,并展示 POI 模板如何处理地图区域变化和地点选择。
怎么开始:申请 parking 类别 entitlement,用 CPPointOfInterestTemplate 提供最多 12 个相关地点,在 delegate 里根据 MKCoordinateRegion 刷新列表。
EV 充电站入口
做什么:给充电 App 增加车屏入口,显示附近充电站、状态摘要和选择按钮。
为什么值得做:EV charging 是 iOS 14 新增的 CarPlay 类别,POI 模板和信息模板正好覆盖找站点、看信息、确认下一步。
怎么开始:把充电站模型映射成 CPPointOfInterest,用 informativeText 放最关键的状态信息,再用 CPInformationTemplate 展示站点详情或确认页。
快餐取餐流程
做什么:让快餐 App 在 CarPlay 上显示附近门店、历史订单、收藏和当前订单摘要。
为什么值得做:演讲里的 quick-service restaurant 示例强调一键访问常见任务,不展示详细菜单、账号管理或设置。
怎么开始:把 CarPlay 体验限定为复购和取餐,用 tab bar 组织门店、历史订单和收藏,用信息模板展示订单摘要。
通信消息状态面板
做什么:为消息或通话 App 做一个 CarPlay 联系人和消息线程入口。
为什么值得做:iOS 14 允许通信 App 使用 CarPlay framework 显示联系人、消息列表和状态,但语音与通话仍由 SiriKit 和 CallKit 承担。
怎么开始:继续维护 SiriKit 与 CallKit 能力,用 CPMessageListItem 表达消息线程状态,用 contact template 提供联系人图片、描述和动作按钮。
关联 Session
- Introducing Car Keys — 同样围绕车内体验,介绍 iPhone 和 Apple Watch 数字车钥匙的注册、共享和安全模型。
- Design high quality Siri media interactions — 补充音频 App 的 Siri 播放质量,和 CarPlay Now Playing 共同影响车内播放体验。
- Streamline your App Clip — 用点单和停车付款拆解现场交易流程,可延伸到快餐点餐与停车场景。
- What’s new in Core NFC — 介绍停车计费器和充电站等实体设备入口,适合作为 CarPlay 地点任务的线下触发补充。
- What’s new in Wallet and Apple Pay — 补充车内停车、快餐点餐和 App Clip 场景可能需要的支付入口。
评论
GitHub Issues · utterances