关于 carplay
面向 Swift 开发者的 CarPlay 开发技能,介绍如何构建按类别授权、基于系统模板的车载应用,并涵盖场景配置、导航、音频、通信和地点信息界面。
主要功能
- 01配置 CarPlay 类别授权
- 02设置 CarPlay 场景与代理
- 03使用系统模板组织界面
- 04构建导航、音频和通信体验
- 05展示兴趣点及地点详情
- 06通过 CarPlay 模拟器测试
适用场景
- 01开发车载导航应用
- 02呈现音频浏览和播放界面
- 03构建车载消息与联系人界面
- 04展示充电、停车或订餐地点
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill carplay安装后在 Claude Code 中输入/carplay确认已加载。
附带文件evals/evals.jsonreferences/carplay-patterns.md
详细介绍
CarPlay
为车载显示屏构建具有类别授权、基于模板的 CarPlay 应用。 范围:Swift 6.3、iOS 26+。
有关完整导航会话、仪表盘场景和高级模板组合等扩展模式,请参阅 references/carplay-patterns.md。
范围边界:完整的 CarPlay 框架应用使用类别授权、CPTemplateApplicationScene、CPTemplateApplicationSceneDelegate、CPInterfaceController 和系统 CPTemplate 导航。CarPlay 可见的 WidgetKit 小组件和 ActivityKit 实时活动是独立的系统体验;应将其实现转交给相应领域,同时在此处保留针对 CarPlay 的验证。
目录
授权与设置
通过 Apple 的 CarPlay 授权申请表申请特定类别的授权,接受附加条款,然后配置下方获批的类别密钥。
各类别的授权密钥
| 授权 | 类别 |
|---|---|
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 | 快速订餐 |
项目配置
- 在开发者门户的“其他功能”中更新 App ID。
- 为更新后的 App ID 生成新的配置描述文件。
- 在 Xcode 中关闭自动签名,并导入 CarPlay 配置描述文件。
- 添加
Entitlements.plist,并将授权密钥设为true。 - 将“代码签名授权”构建设置设为
Entitlements.plist的路径。
关键类型
| 类型 | 作用 |
|---|---|
CPTemplateApplicationScene | 用于 CarPlay 显示屏的 UIScene 子类 |
CPTemplateApplicationSceneDelegate | 场景连接和断开的生命周期管理 |
CPInterfaceController | CarPlay 提供的控制器,用于设置根模板,以及推入、呈现或弹出模板 |
CPTemplate | 所有 CarPlay 模板的抽象基类 |
CPSessionConfiguration | 车载显示屏限制和内容样式 |
场景配置
在 Info.plist 中声明 CarPlay 场景,并实现 CPTemplateApplicationSceneDelegate,以响应 CarPlay 连接事件。
Info.plist 场景清单
<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>
场景代理(非导航)
非导航应用只会收到接口控制器,不会收到窗口。
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
}
}
场景代理(导航)
导航应用会同时收到接口控制器和 CPWindow。
将窗口的根视图控制器设为用于绘制地图内容的控制器。
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 | 包含选项卡式子模板的容器 |
CPListTemplate | 可滚动的分区列表 |
CPGridTemplate | 可点击的图标按钮网格(最多 8 个) |
CPInformationTemplate | 键值信息,最多可包含 3 个操作 |
CPAlertTemplate | 最多包含 2 个操作的模态提醒 |
CPActionSheetTemplate | 模态操作菜单 |
特定类别的模板
| 模板 | 类别 |
|---|---|
CPMapTemplate | 导航——带导航栏的地图叠加层 |
CPSearchTemplate | 导航——目的地搜索 |
CPNowPlayingTemplate | 音频——共享的“正在播放”屏幕 |
CPPointOfInterestTemplate | 电动汽车充电/停车/餐饮——兴趣点地图 |
CPContactTemplate | 通信——联系人卡片 |
导航层级
使用 pushTemplate(_:animated:completion:) 将模板添加到堆栈。
使用 presentTemplate(_:animated:completion:) 以模态方式显示模板。
使用 popTemplate(animated:completion:) 返回。
CPTabBarTemplate 必须设为根模板——不能推入堆栈或以模态方式呈现。
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) {
// Navigate to selected destination
completionHandler()
}
}
音频应用
音频应用使用 com.apple.developer.carplay-audio。它们在列表中显示可浏览的内容,并使用 CPNowPlayingTemplate 提供播放控制。拥有音频权限的应用无法使用 CPInformationTemplate。
“正在播放”模板
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) // Register as CPNowPlayingTemplateObserver
Siri 助手单元格
支持 INPlayMediaIntent 的音频应用可以显示助手单元格。
通信应用使用带有 .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 没有由应用提供的选择处理程序。选中后,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,
trailingConfiguration: trailing,
detailText: "Meeting at 3pm",
trailingText: "2:45 PM")
let messageList = CPListTemplate(title: "Messages",
sections: [CPListSection(items: [message])])
兴趣点应用
电动汽车充电、停车和食品订购应用使用 CPPointOfInterestTemplate 和 CPInformationTemplate 来显示地点和详情。
CPPointOfInterestTemplate 最多显示 12 个兴趣点。
CPPointOfInterestTemplate
let poi = CPPointOfInterest(
location: MKMapItem(placemark: MKPlacemark(
coordinate: CLLocationCoordinate2D(latitude: 37.7749,
longitude: -122.4194))),
title: "SuperCharger Station", subtitle: "4 available",
summary: "150 kW DC fast charging",
detailTitle: "SuperCharger Station", detailSubtitle: "$0.28/kWh",
detailSummary: "Open 24 hours",
pinImage: UIImage(systemName: "bolt.fill"))
poi.primaryButton = CPTextButton(title: "Navigate",
textStyle: .confirm) { _ in }
let poiTemplate = CPPointOfInterestTemplate(
title: "Nearby Chargers", pointsOfInterest: [poi], selectedIndex: 0)
poiTemplate.pointOfInterestDelegate = self
CPInformationTemplate
let infoTemplate = CPInformationTemplate(
title: "Order Summary", layout: .leading,
items: [
CPInformationItem(title: "Item", detail: "Burrito Bowl"),
CPInformationItem(title: "Total", detail: "$12.50")],
actions: [
CPTextButton(title: "Place Order", textStyle: .confirm) { _ in
self.placeOrder() },
CPTextButton(title: "Cancel", textStyle: .cancel) { _ in
self.interfaceController?.popTemplate(animated: true,
completion: nil) }])
使用 CarPlay 模拟器测试
- 在 Xcode 中使用 iOS 模拟器构建并运行。
- 选择“输入/输出” >“外接显示器” >“CarPlay”。
默认窗口:800x480,比例为 @2x。为导航应用启用额外选项:
defaults write com.apple.iphonesimulator CarPlayExtraOptions -bool YES
推荐的测试配置
| 配置 | 像素 | 缩放比例 |
|---|---|---|
| 最小 | 748 x 456 | @2x |
| 纵向 | 768 x 1024 | @2x |
| 标准 | 800 x 480 | @2x |
| 高分辨率 | 1920 x 720 | @3x |
模拟器无法测试 iPhone 锁定时的行为、Siri、与汽车收音机同时播放音频的情况,也无法测试实体输入硬件(旋钮、触控板)。尽可能在支持 CarPlay 的真实车辆或售后主机上进行测试。设计 CarPlay 的主要工作流时,应确保在 CarPlay 运行期间无需操作 iPhone。
常见错误
不要:使用错误的场景代理方法
导航应用必须实现 templateApplicationScene(_:didConnect:to:)(带 CPWindow)。非导航应用使用 templateApplicationScene(_:didConnect:)(不带窗口)。使用错误的变体会导致 CarPlay 界面无法显示。
不要:在导航窗口中绘制自定义界面
CPWindow 专用于地图内容。所有叠加层、提醒和控件都必须使用 CarPlay 模板。
不要:推入堆栈或以模态方式呈现 CPTabBarTemplate
CPTabBarTemplate 只能设为根模板。推入堆栈或以模态方式呈现都会失败。请使用 setRootTemplate(_:animated:completion:)。
不要:创建 CPNowPlayingTemplate 实例
请使用 CPNowPlayingTemplate.shared。创建新实例会导致问题。
不要:为 CPMessageListItem 添加处理程序
CPMessageListItem 由 Siri 管理,与 CPListItem 不同。不要设置
message.handler;请使用项目配置和 userInfo 来传递上下文。
不要:将小组件视为 CarPlay 模板应用
CarPlay 中可见的小组件和实时活动属于 WidgetKit 和 ActivityKit。对于需要类别权限的 CarPlay 模板应用场景,以及在车载环境中验证这些界面,请使用此技能。
不要:忽略车辆显示限制
检查 CPSessionConfiguration.limitedUserInterfaces,并遵守列表模板的
maximumItemCount / maximumSectionCount 限制。
不要:忘记调用完成处理程序
CPListItem.handler 必须在所有代码路径中调用其完成处理程序。
否则列表会一直处于加载状态。
审查清单
-
Entitlements.plist中的 CarPlay 权限键正确 -
UIApplicationSupportsMultipleScenes设置为true - Info.plist 中包含
CPTemplateApplicationSceneSessionRoleApplication场景 - 场景代理类名与
UISceneDelegateClassName匹配 - 使用了正确的代理方法(带或不带
CPWindow) - 在
didConnect中返回前设置根模板 - 断开连接时清除界面控制器和窗口引用
-
CPTabBarTemplate仅用作根模板,绝不推入导航栈 - 使用
CPNowPlayingTemplate.shared,而非新建实例 - 通信列表行使用不带自定义处理程序的
CPMessageListItem - WidgetKit/ActivityKit 界面由 CarPlay 模板应用代码之外的路径处理
- 填充列表前检查
maximumItemCount/maximumSectionCount -
CPListItem.handler在所有路径中都调用完成处理程序 - 仅地图内容放在
CPWindow根视图控制器中(导航应用) - iPhone 锁定时应用仍能正常运行
- 在最低、标准和高分辨率模拟器尺寸下进行测试
- 不主动播放时停用音频会话