关于 carplay
carplay 是面向 iOS CarPlay 开发者的参考技能,涵盖十类应用及相关框架、模板和界面能力。内容还涉及权限要求、通知、导航、车载组件与模拟器测试。
主要功能
- 01覆盖十类 CarPlay 应用
- 02提供模板、界面与导航 API 参考
- 03说明各类别权限与模板限制
- 04涵盖通知、组件和实时活动
- 05提供 CarPlay Simulator 测试指引
适用场景
- 01开发 CarPlay 音频、播客或音乐应用
- 02实现导航、通信、停车或充电应用
- 03为 CarPlay 添加通知、Widget 或 Live Activity
- 04使用模拟器测试多种车载屏幕尺寸
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ios-agent/ iosagent.dev --skill carplay安装后在 Claude Code 中输入/carplay确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api_reference.mdaudio.mdcommunication.mddashboard.mddeveloper_guide.mdgetting_started.mdindex.mdnavigation.mdpoi.mdtemplates.mdui_elements.md详细介绍
CarPlay 技能
使用此技能的场景
使用此技能的场景:
- 为 iOS 开发 CarPlay 应用
- 为 CarPlay 构建音频、播客或音乐应用
- 创建提供逐向导航的导航应用
- 实现通信应用(消息、VoIP)
- 构建停车、EV 充电或点餐应用
- 构建驾驶任务、加油或公共安全应用
- 创建基于语音的对话应用(iOS 26.4+)
- 为 CarPlay 添加小组件或实时活动(iOS 26+)
- 实现 CarPlay 通知
- 在 CarPlay 地图上启用多点触控导航(iOS 26+)
- 提供仪表盘或 HUD 元数据(iOS 17.4+)
- 使用 CPTemplate、CPInterfaceController 或任何 CarPlay API
描述
完整的 CarPlay 框架文档,涵盖全部 10 种应用类别:音频、通信、导航、停车、EV 充电、快速点餐、加油、驾驶任务、公共安全和基于语音的对话。包含所有模板、UI 元素、导航 API、小组件、实时活动、CarPlay Ultra、通知、仪表盘元数据和多点触控导航。
快速参考
核心组件
API 参考
CPInterfaceControllerCPWindowCarPlay
通信
CPContactCPMessageListItem
入门
CPSessionConfigurationCPTemplateApplicationSceneCPTemplateApplicationSceneDelegateCPTemplateApplicationScene.open(_:options:completionHandler:)
导航
CPManeuverCPNavigationSessionCPRouteChoiceCPRouteInformationCPTripCPManeuverTypeCPJunctionTypeCPTrafficSideCPLaneStatus
兴趣点
CPPointOfInterest
模板
CPActionSheetTemplateCPAlertTemplateCPContactTemplateCPGridTemplateCPInformationTemplateCPListTemplateCPMapTemplateCPNowPlayingTemplateCPPointOfInterestTemplateCPSearchTemplateCPTabBarTemplateCPTemplateCPVoiceControlTemplate
UI 元素
CPAlertActionCPBarButtonCPButtonCPGridButtonCPInformationItemCPListImageRowItemCPListItemCPListSectionCPTextButton
仪表盘(iOS 13.4+)
CPDashboardButtonCPDashboardControllerCPTemplateApplicationDashboardSceneCPTemplateApplicationDashboardSceneDelegate
仪表盘(iOS 15.4+)
CPInstrumentClusterControllerCPTemplateApplicationInstrumentClusterSceneCPTemplateApplicationInstrumentClusterSceneDelegate
通知
UNAuthorizationOptions.carPlay.allowInCarPlay
小组件和实时活动
.supportedFamilies.supplementalActivityFamilies
关键概念
平台支持
- iOS 12.0+(Core CarPlay)
- iOS 13.4+(仪表盘场景)
- iOS 14.0+(标签栏、POI、正在播放)
- iOS 15.4+(仪表盘)
- iOS 16.0+(加油、驾驶任务类别;POI selectedPinImage;Information 导航栏按钮)
- iOS 17.4+(CPRouteInformation、resumeTrip、导航元数据、重新规划路线)
- iOS 18.4+(正在播放的体育模式)
- iOS 26+(CarPlay 中的小组件、CarPlay 中的实时活动、多点触控导航、列表元素样式)
- iOS 26.4+(基于语音的对话应用、语音控制操作和导航栏按钮)
- iPadOS 12.0+
- Mac Catalyst 13.1+
CarPlay 应用类别
**⚠️ 重要:**CarPlay 应用需要获得 Apple 授权。应用连接到 CarPlay 前,必须针对特定应用类别申请并获得批准。
CarPlay 支持以下 10 个类别的应用(每个类别都需要单独的授权):
-
音频和播客(iOS 12+,深度限制:5)
- 授权:
com.apple.developer.carplay-audio - 要求:音频播放、与“正在播放”集成、
MPNowPlayingInfoCenter
- 授权:
-
通信(iOS 12+,深度限制:5)
- 授权:
com.apple.developer.carplay-communication - 要求:集成 CallKit、集成 SiriKit
- 授权:
-
导航(iOS 12+,深度限制:5)
- 授权:
com.apple.developer.carplay-maps - 要求:实时导航、路线指引、地图渲染
- 授权:
-
停车(iOS 14+,深度限制:5)
- 授权:
com.apple.developer.carplay-parking - 要求:定位服务、停车位可用情况
- 授权:
-
电动汽车充电(iOS 14+,深度限制:5)
- 授权:
com.apple.developer.carplay-charging - 要求:充电站数据、可用状态
- 授权:
-
快捷点餐(iOS 14+,深度限制:3)
- 授权:
com.apple.developer.carplay-quick-ordering - 要求:精简的菜单浏览、下单、取餐
- 授权:
-
加油(iOS 16+,深度限制:3)
- 授权:
com.apple.developer.carplay-fueling - 要求:加油站位置、油泵控制、支付
- 授权:
-
驾驶任务(iOS 16+,深度限制:3)
- 授权:
com.apple.developer.carplay-driving-task - 要求:驾驶时完成的任务(路况、收费站、配件)。不得与导航功能重复。
- 授权:
-
公共安全(iOS 14+,深度限制:3)
- 授权:
com.apple.developer.carplay-public-safety - 要求:实时安全信息(危险、测速摄像头、紧急警报)
- 授权:
-
语音对话(iOS 26.4+,深度限制:3)
- 授权:
com.apple.developer.carplay-voice-based-conversation - 要求:主要通过语音交互。使用
CPVoiceControlTemplate作为主要 UI。
- 授权:
如何申请授权:
- 访问 https://developer.apple.com/contact/carplay/
- 说明应用的功能和类别
- 等待 Apple 审核并批准(通常需要 1-2 周)
- 将获批的授权添加到 Xcode 项目中
基于模板的 UI
CarPlay 使用基于模板的系统,Apple 提供 UI 组件,你提供内容。这可以确保:
- 针对车内使用优化、适合驾驶员操作的界面
- 所有 CarPlay 应用都具有一致的用户体验
- 自动适配不同车辆的显示屏
模板可用性矩阵
并非所有类别都能使用所有模板。主要限制如下:
- 仅限导航:
CPMapTemplate - 仅限通信:
CPContactTemplate - 音频应用:
CPTabBarTemplate最多 4 个标签页;其他类别最多 5 个标签页 - **点餐 / 加油 / 驾驶任务 / 公共安全 / 语音对话:**模板深度限制为 3(音频、通信、导航、停车和电动汽车充电类别为 5)
各类别完整的模板列表请参阅 references/developer_guide.md。
CarPlay Ultra
CarPlay Ultra 是 Apple 的下一代车载体验,可深度集成车辆的显示屏,包括仪表盘、中控台和乘客显示屏。支持 CarPlay 的应用会自动兼容 CarPlay Ultra,无需额外采用 API。CarPlay Ultra 使用相同的基于模板的系统和授权。
CarPlay 中的小组件(iOS 26+)
WidgetKit 小组件可以显示在 CarPlay 主屏幕上。要支持 CarPlay:
- 将
.accessoryCircular或.accessoryRectangular添加到小组件的supportedFamilies - 保持小组件内容简单明了,便于快速浏览;不要添加交互式控件
- 小组件按照与 iPhone 相同的时间线刷新
CarPlay 中的实时活动(iOS 26+)
实时活动可以显示在 CarPlay 中,提供实时更新(配送跟踪、体育比分、乘车状态)。启用方法:
- 在
ActivityConfiguration的supplementalActivityFamilies中添加.carPlay - 使用适合驾驶场景的紧凑和最小化呈现方式
- 实时活动会以常驻横幅的形式显示在 CarPlay 界面中
语音对话类 App(iOS 26.4+)
这是面向 AI 助手和语音驱动体验的全新 App 类别。该类别以 CPVoiceControlTemplate 作为主要界面,并包含:
- 语音可视化区域下方的快捷命令操作按钮
- 用于辅助控制的导航栏按钮
- 极简视觉界面——交互主要通过语音进行
CarPlay 中的通知
App 可以向 CarPlay 发送通知。要求:
- 请求通知权限时,在
UNAuthorizationOptions中请求.carPlay - 对于应显示在 CarPlay 屏幕上的通知类别,在
UNNotificationCategory上设置.allowInCarPlay - 通知内容应简短,并与驾驶场景相关
资源尺寸指南
| 资源 | 建议尺寸 |
|---|---|
| 列表项图像 | 44×44 pt(@2x:88×88 px,@3x:132×132 px) |
| 网格按钮图像 | 44×44 pt |
| 地图按钮图像 | 44×44 pt |
| 标签栏图标 | 30×30 pt |
| 提醒图像 | 44×44 pt |
| POI 图钉图像 | 44×44 pt |
| 导航转向符号 | 44×44 pt |
| 正在播放的作品图像 | 44×44 pt(缩略图),最大 256×256 pt(完整尺寸) |
音频处理
- 音频 App 使用
AVAudioSession类别.playback - 导航语音提示应使用模式
.voicePrompt,并搭配选项.duckOthers和.interruptSpokenAudioAndMixWithOthers - 音频 App 必须响应
MPRemoteCommandCenter事件(播放、暂停、跳过) - 播放前激活音频会话;播放结束后停用
iPhone 锁定时访问数据
iPhone 锁定时,文件访问可能会受限。对于 CarPlay App 需要使用的文件,请使用 FileProtectionType.completeUntilFirstUserAuthentication 或 .none。默认值 .completeUnlessOpen 可能会导致无法访问。
启动其他 App
CarPlay App 可以使用 CPTemplateApplicationScene.open(_:options:completionHandler:) 打开其他 App。这适用于跳转到基于手机的流程(例如完成购买)。系统会在切换前提示用户。
使用指南
- 使用适合 App 类别的模板
- 保持内容简洁 - 为保障驾驶安全,限制项目数量和文本
- 提供清晰的大尺寸图像,以提高可见性
- 妥善处理连接和断开连接
- 先在 CarPlay 模拟器中测试,再在车辆中测试
- 遵守减少分心的指南 - 驾驶时不得播放视频、动画或广告
使用 CarPlay 模拟器进行测试
测试 CarPlay 有两种方式:
选项 1:Xcode 模拟器窗口
- 在 Xcode 中打开 CarPlay 应用项目
- 选择一个 iOS 模拟器作为运行目标
- 在模拟器菜单中选择:I/O > External Displays > CarPlay
- CarPlay 显示窗口会与 iOS 模拟器一同出现
选项 2:独立 CarPlay 模拟器(Mac 应用)
- 从 developer.apple.com 下载 Additional Tools for Xcode
- 安装 CarPlay Simulator 应用
- 将实体 iPhone 连接到 Mac
- CarPlay Simulator 会连接到设备并显示 CarPlay 界面
启用额外选项:
defaults write com.apple.iphonesimulator CarPlayExtraOptions -bool YES
这会启用屏幕尺寸选择和仪表盘显示设置等额外选项。
测试用导航屏幕尺寸:
| 屏幕尺寸 | 缩放比例 |
|---|---|
| 748 × 456 | @2x |
| 800 × 480 | @2x |
| 960 × 540 | @2x |
| 1024 × 600 | @2x |
| 1080 × 600 | @2x |
| 1280 × 720 | @2x |
| 900 × 1200 | @3x |
模拟器功能:
- 测试所有 CarPlay 模板和 UI 元素
- 模拟日间/夜间模式(根据系统深色模式自动切换)
- 测试不同的屏幕尺寸和宽高比
- 模拟 Siri 交互和语音命令
- 测试连接/断开连接场景
- 测试仪表盘渲染(启用额外选项后)
限制:
- 无法测试与真实车辆的集成
- 对实体控制装置(旋钮、触摸屏)的测试有限
- 某些车辆专属功能不可用
- 音频路由可能与真实车辆不同
**最佳实践:**务必先在模拟器中针对多种屏幕尺寸进行测试,然后在发布前通过实体车辆或兼容 CarPlay 的主机进行验证。
导航
按类别整理的详细 API 文档请参阅 references/ 目录:
references/api_reference.md- API 参考references/communication.md- 通信references/dashboard.md- 仪表盘和仪表组references/developer_guide.md- 开发者指南(类别、操作指南、测试、发布)references/getting_started.md- 入门references/navigation.md- 导航references/poi.md- 兴趣点references/templates.md- 模板references/ui_elements.md- UI 元素
最佳实践
- 场景管理:实现
CPTemplateApplicationSceneDelegate以处理 CarPlay 连接 - 模板限制:遵守最大项目数限制(CPListTemplate:12 个分区,CPGridTemplate:8 个按钮)
- 模板深度限制:音频、通信、导航、停车和电动汽车充电应用:最多 5 层模板。食品订购、加油、驾驶任务、公共安全和语音应用:最多 3 层模板。
- 图像尺寸:遵循模板和 UI 元素的建议图像尺寸(参见资源尺寸指南)
- 动态更新:使用更新方法(
updateSections、updateButtons)刷新内容 - 导航层级:使用 push/pop 进行导航,使用 present 显示模态界面
- 音频会话管理:适当配置
AVAudioSession——音频应用使用.playback,导航语音提示使用.voicePrompt模式。完成后停用会话。 - 文件保护:对于 iPhone 锁定时仍需使用的文件,使用
.completeUntilFirstUserAuthentication - 通知:仅为与驾驶相关的通知类别启用
.allowInCarPlay。保持内容简短。 - 错误处理:妥善处理失败并向用户提供反馈
常见模式
基本列表模板
// Create list items
let item1 = CPListItem(text: "Song Title", detailText: "Artist Name")
item1.handler = { item, completion in
// Handle selection
completion()
}
// Create section and template
let section = CPListSection(items: [item1, item2])
let listTemplate = CPListTemplate(title: "My Playlist", sections: [section])
// Set as root
interfaceController.setRootTemplate(listTemplate, animated: true)
使用地图模板进行导航
// Create map template
let mapTemplate = CPMapTemplate()
mapTemplate.mapDelegate = self
// Show trip preview
let trip = CPTrip(/* route data */)
mapTemplate.showTripPreviews([trip], textConfiguration: nil)
// Start navigation
let navigationSession = mapTemplate.startNavigationSession(for: trip)
navigationSession.upcomingManeuvers = [maneuver1, maneuver2]
标签栏模板
// Create root templates for each tab
let musicTab = CPListTemplate(title: "Music", sections: [musicSection])
musicTab.tabTitle = "Music"
musicTab.tabImage = UIImage(systemName: "music.note")
let podcastsTab = CPListTemplate(title: "Podcasts", sections: [podcastSection])
podcastsTab.tabTitle = "Podcasts"
podcastsTab.tabImage = UIImage(systemName: "mic")
// Create tab bar
let tabBarTemplate = CPTabBarTemplate(templates: [musicTab, podcastsTab])
interfaceController.setRootTemplate(tabBarTemplate, animated: false)
正在播放模板
// Configure Now Playing buttons
let playButton = CPNowPlayingPlaybackRateButton()
let skipButton = CPNowPlayingSkipButton(handler: { _ in
// Skip to next track
})
CPNowPlayingTemplate.shared.updateNowPlayingButtons([playButton, skipButton])
CPNowPlayingTemplate.shared.isUpNextButtonEnabled = true
实时赛事的体育模式(iOS 18.4+)
// Create team logos
let homeTeamLogo = CPNowPlayingSportsTeamLogo(teamLogo: UIImage(named: "home_logo")!)
let awayTeamLogo = CPNowPlayingSportsTeamLogo(teamInitials: "VIS")
// Create teams with scores and possession
let homeTeam = CPNowPlayingSportsTeam(
name: "Home Team",
logo: homeTeamLogo,
teamStandings: "1st Place",
eventScore: "21",
possessionIndicator: .solid, // Home team has possession
favorite: true
)
let awayTeam = CPNowPlayingSportsTeam(
name: "Away Team",
logo: awayTeamLogo,
teamStandings: "2nd Place",
eventScore: "17",
possessionIndicator: nil, // No possession
favorite: false
)
// Create game clock (counting down from 15:00)
let gameClock = CPNowPlayingSportsClock(
timeRemaining: 15 * 60, // 15 minutes in seconds
paused: false
)
// Create event status with status text and clock
let eventStatus = CPNowPlayingSportsEventStatus(
eventStatusText: ["3rd Quarter", "4th Down", "Goal Line"],
eventStatusImage: UIImage(named: "field_position"),
eventClock: gameClock
)
// Create sports mode
let sportsMode = CPNowPlayingModeSports(
leftTeam: homeTeam,
rightTeam: awayTeam,
eventStatus: eventStatus,
backgroundArtwork: UIImage(named: "stadium_background")
)
// Set as active now playing mode
CPNowPlayingTemplate.shared.nowPlayingMode = sportsMode
// Update clock during game (e.g., every second)
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
// Update time remaining
let newClock = CPNowPlayingSportsClock(
timeRemaining: getCurrentGameTime(),
paused: isGamePaused()
)
// Update event status with new clock
let updatedStatus = CPNowPlayingSportsEventStatus(
eventStatusText: eventStatus.eventStatusText,
eventStatusImage: eventStatus.eventStatusImage,
eventClock: newClock
)
// Refresh sports mode
let updatedMode = CPNowPlayingModeSports(
leftTeam: homeTeam,
rightTeam: awayTeam,
eventStatus: updatedStatus,
backgroundArtwork: sportsMode.backgroundArtwork
)
CPNowPlayingTemplate.shared.nowPlayingMode = updatedMode
}
使用转向指引进行导航
// Create maneuvers for turn-by-turn guidance
let maneuver = CPManeuver()
maneuver.instructionVariants = ["Turn right onto Main Street"]
maneuver.symbolImage = UIImage(named: "turn-right")
maneuver.initialTravelEstimates = CPTravelEstimates(
distanceRemaining: Measurement(value: 500, unit: UnitLength.meters),
timeRemaining: 60
)
// Add lane guidance
let lane1 = CPLane(angles: [.left, .straight])
lane1.status = .recommended
let lane2 = CPLane(angles: [.straight, .right])
lane2.status = .preferred
maneuver.linkedLaneGuidance = CPLaneGuidance(lanes: [lane1, lane2], instructionVariants: ["Use right lane"])
// Add maneuver to navigation session
navigationSession.upcomingManeuvers = [maneuver]
带图像的属性化指引
// Create attributed instruction with embedded image
let instruction = NSMutableAttributedString(string: "Turn right on Apple Park Way")
// Attach an image icon
let image = UIImage(systemName: "arrow.turn.up.right")!
let attachment = NSTextAttachment(image: image)
let imageString = NSAttributedString(attachment: attachment)
instruction.append(NSAttributedString(string: " "))
instruction.append(imageString)
// Create maneuver with attributed instruction
let maneuver = CPManeuver()
maneuver.attributedInstructionVariants = [instruction]
// Also set dashboard-specific attributed instructions
let dashboardInstruction = NSMutableAttributedString(string: "Turn Right ")
let dashboardImage = UIImage(systemName: "arrow.turn.up.right.circle.fill")!
let dashboardAttachment = NSTextAttachment(image: dashboardImage)
dashboardInstruction.append(NSAttributedString(attachment: dashboardAttachment))
dashboardInstruction.append(NSAttributedString(string: " Apple Park Way"))
maneuver.dashboardAttributedInstructionVariants = [dashboardInstruction]
// Set symbol image for main display
maneuver.symbolImage = UIImage(named: "turn-right")
// Add to navigation session
navigationSession.upcomingManeuvers = [maneuver]
路线选择
// Create route choices for trip preview
let fastRoute = CPRouteChoice(
summaryVariants: ["Via Highway - 25 min"],
additionalInformationVariants: ["Fastest route"],
selectionSummaryVariants: ["Take the highway"]
)
let scenicRoute = CPRouteChoice(
summaryVariants: ["Via Scenic Road - 35 min"],
additionalInformationVariants: ["No tolls"],
selectionSummaryVariants: ["Take the scenic route"]
)
let trip = CPTrip(
origin: originMapItem,
destination: destinationMapItem,
routeChoices: [fastRoute, scenicRoute]
)
mapTemplate.showTripPreviews([trip], textConfiguration: nil)
仪表盘集成(iOS 13.4+)
// Configure dashboard scene delegate
func templateApplicationDashboardScene(
_ dashboardScene: CPTemplateApplicationDashboardScene,
didConnect dashboardController: CPDashboardController,
to window: UIWindow
) {
// Create dashboard shortcut buttons
let homeButton = CPDashboardButton(
titleVariants: ["Home"],
subtitleVariants: ["123 Main St"],
image: UIImage(systemName: "house.fill")!
) { button in
// Navigate to home
}
let workButton = CPDashboardButton(
titleVariants: ["Work"],
subtitleVariants: ["456 Office Blvd"],
image: UIImage(systemName: "building.2.fill")!
) { button in
// Navigate to work
}
// Set dashboard shortcut buttons
dashboardController.shortcutButtons = [homeButton, workButton]
}
仪表组集成(iOS 15.4+)
// Configure instrument cluster scene delegate
func templateApplicationInstrumentClusterScene(
_ instrumentClusterScene: CPTemplateApplicationInstrumentClusterScene,
didConnect instrumentClusterController: CPInstrumentClusterController
) {
// Configure instrument cluster display
instrumentClusterController.inactiveDescriptionVariants = [
"No active navigation"
]
// Set compass display
instrumentClusterController.compassSetting = .automatic
// Update with route information (iOS 17.4+)
let routeInfo = CPRouteInformation(
maneuvers: upcomingManeuvers,
laneGuidances: lanGuidances,
currentManeuvers: [currentManeuver],
currentLaneGuidance: currentLaneGuidance,
trip: currentTrip,
maneuverTravelEstimates: travelEstimates
)
// Display on instrument cluster
instrumentClusterController.compassSetting = .showAlways
}
网格模板
// Create grid buttons with icons
let homeButton = CPGridButton(
titleVariants: ["Home"],
image: UIImage(systemName: "house.fill")!
) { button in
// Navigate to home
}
let workButton = CPGridButton(
titleVariants: ["Work"],
image: UIImage(systemName: "building.2.fill")!
) { button in
// Navigate to work
}
let favoritesButton = CPGridButton(
titleVariants: ["Favorites"],
image: UIImage(systemName: "star.fill")!
) { button in
// Show favorites
}
// Create grid template with up to 8 buttons
let gridTemplate = CPGridTemplate(
title: "Quick Actions",
gridButtons: [homeButton, workButton, favoritesButton]
)
interfaceController.setRootTemplate(gridTemplate, animated: true)
// Update buttons dynamically
let newButtons = [homeButton, workButton, favoritesButton, newButton]
gridTemplate.updateGridButtons(newButtons)
搜索模板
// Create search template
let searchTemplate = CPSearchTemplate()
searchTemplate.delegate = self
// Set as root
interfaceController.setRootTemplate(searchTemplate, animated: true)
// Implement CPSearchTemplateDelegate
func searchTemplate(
_ searchTemplate: CPSearchTemplate,
updatedSearchText searchText: String,
completionHandler: @escaping ([CPListItem]) -> Void
) {
// Perform search
let results = performSearch(query: searchText)
// Convert to list items
let listItems = results.map { result in
let item = CPListItem(text: result.name, detailText: result.address)
item.handler = { item, completion in
// Handle selection
self.showResultDetails(result)
completion()
}
return item
}
// Return results
completionHandler(listItems)
}
func searchTemplateSearchButtonPressed(_ searchTemplate: CPSearchTemplate) {
// User tapped search button
print("Search initiated")
}
兴趣点模板
// Create points of interest
let poi1 = CPPointOfInterest(
location: MKMapItem(placemark: MKPlacemark(coordinate: coordinate1)),
title: "Apple Park",
subtitle: "1 Apple Park Way",
summary: "Apple headquarters",
detailTitle: "Visitor Center",
detailSubtitle: "Open 9 AM - 5 PM",
detailSummary: "Tours available",
pinImage: UIImage(systemName: "applelogo")
)
let poi2 = CPPointOfInterest(
location: MKMapItem(placemark: MKPlacemark(coordinate: coordinate2)),
title: "Golden Gate Bridge",
subtitle: "San Francisco",
summary: "Iconic landmark",
detailTitle: "Vista Point",
detailSubtitle: "Always Open",
detailSummary: "Great views",
pinImage: UIImage(systemName: "bridge")
)
// Create POI template
let poiTemplate = CPPointOfInterestTemplate(
title: "Nearby Attractions",
pointsOfInterest: [poi1, poi2],
selectedIndex: 0
)
poiTemplate.pointOfInterestDelegate = self
// Implement delegate
func pointOfInterestTemplate(
_ template: CPPointOfInterestTemplate,
didSelectPointOfInterest poi: CPPointOfInterest
) {
// User selected a POI
print("Selected: \(poi.title)")
// Show navigation options
showNavigationOptions(for: poi)
}
func pointOfInterestTemplate(
_ template: CPPointOfInterestTemplate,
didChangeMapRegion region: MKCoordinateRegion
) {
// Map region changed (user panned/zoomed)
fetchNearbyPOIs(in: region)
}
信息模板
// Create information items
let addressItem = CPInformationItem(
title: "Address",
detail: "1 Apple Park Way, Cupertino, CA"
)
let hoursItem = CPInformationItem(
title: "Hours",
detail: "9:00 AM - 9:00 PM"
)
let phoneItem = CPInformationItem(
title: "Phone",
detail: "(408) 996-1010"
)
// Create action buttons
let callButton = CPTextButton(text: "Call", textStyle: .normal) { button in
// Make phone call
makePhoneCall()
}
let directionsButton = CPTextButton(text: "Directions", textStyle: .confirm) { button in
// Start navigation
startNavigation()
}
// Create information template
let infoTemplate = CPInformationTemplate(
title: "Apple Store",
layout: .leading, // or .twoColumn
items: [addressItem, hoursItem, phoneItem],
actions: [callButton, directionsButton]
)
interfaceController.pushTemplate(infoTemplate, animated: true)
操作表模板
// Create actions for action sheet
let confirmAction = CPAlertAction(title: "Confirm", style: .default) { action in
// User confirmed
self.performAction()
}
let cancelAction = CPAlertAction(title: "Cancel", style: .cancel) { action in
// User cancelled
print("Action cancelled")
}
let destructiveAction = CPAlertAction(title: "Delete", style: .destructive) { action in
// User chose destructive action
self.deleteItem()
}
// Create action sheet
let actionSheet = CPActionSheetTemplate(
title: "Choose Action",
message: "What would you like to do?",
actions: [confirmAction, destructiveAction, cancelAction]
)
// Present modally
interfaceController.presentTemplate(actionSheet, animated: true, completion: nil)
警告模板
// Create alert actions
let okAction = CPAlertAction(title: "OK", style: .default) { action in
// User tapped OK
print("User acknowledged")
}
let retryAction = CPAlertAction(title: "Retry", style: .default) { action in
// User wants to retry
self.retryOperation()
}
let cancelAction = CPAlertAction(title: "Cancel", style: .cancel) { action in
// User cancelled
}
// Create alert with multiple title variants for different screen sizes
let alert = CPAlertTemplate(
titleVariants: [
"Connection Lost",
"Lost Connection",
"No Network"
],
actions: [retryAction, cancelAction]
)
// Present alert
interfaceController.presentTemplate(alert, animated: true, completion: nil)
// Auto-dismiss after 5 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
interfaceController.dismissTemplate(animated: true, completion: nil)
}
语音控制模板
// Create voice control states
let listeningState = CPVoiceControlState(
identifier: "listening",
titleVariants: ["Listening...", "Say a command"],
image: UIImage(systemName: "waveform")!,
repeats: true
)
let processingState = CPVoiceControlState(
identifier: "processing",
titleVariants: ["Processing...", "Working on it"],
image: UIImage(systemName: "gear")!,
repeats: false
)
let successState = CPVoiceControlState(
identifier: "success",
titleVariants: ["Done!", "Complete"],
image: UIImage(systemName: "checkmark.circle.fill")!,
repeats: false
)
// Create voice control template
let voiceTemplate = CPVoiceControlTemplate(
voiceControlStates: [listeningState, processingState, successState]
)
// Present voice control
interfaceController.presentTemplate(voiceTemplate, animated: true) {
// Start listening
self.startVoiceRecognition()
}
// Update state based on recognition progress
voiceTemplate.activateVoiceControlState(withIdentifier: "processing")
// Show success when done
voiceTemplate.activateVoiceControlState(withIdentifier: "success")
// Dismiss after completion
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
interfaceController.dismissTemplate(animated: true, completion: nil)
}
联系人模板(通信)
// Create contact
let contact = CPContact(
name: "John Appleseed",
image: UIImage(named: "contact_photo")
)
contact.subtitle = "Friend"
contact.informativeText = "Last contact: Today"
// Create contact action buttons
let callButton = CPContactCallButton(handler: { button in
// Initiate phone call
self.makePhoneCall(to: contact)
})
let messageButton = CPContactMessageButton(phoneOrEmail: "[email protected]")
let directionsButton = CPContactDirectionsButton(handler: { button in
// Navigate to contact's location
self.navigateToContact(contact)
})
contact.actions = [callButton, messageButton, directionsButton]
// Create contact template
let contactTemplate = CPContactTemplate(contact: contact)
// Present contact card
interfaceController.pushTemplate(contactTemplate, animated: true)
消息列表模板(通信)
// Create leading configurations (profile images)
let profileImage = UIImage(named: "profile")!
let leadingConfig = CPMessageListItemLeadingConfiguration(
leadingItem: .image(profileImage),
leadingImage: profileImage,
unreadIndicator: true
)
// Create trailing configurations (timestamps/badges)
let trailingConfig = CPMessageListItemTrailingConfiguration(
trailingItem: .image(UIImage(systemName: "chevron.right")!),
trailingImage: UIImage(systemName: "paperclip")
)
// Create message list items
let message1 = CPMessageListItem(
conversationIdentifier: "conv_1",
text: "John Appleseed",
leadingConfiguration: leadingConfig,
trailingConfiguration: trailingConfig,
detailText: "Hey, are you free for lunch?",
trailingText: "12:30 PM"
)
message1.handler = { item, completion in
// Open conversation
self.openConversation(id: "conv_1")
completion()
}
let message2 = CPMessageListItem(
fullName: "Jane Smith",
phoneOrEmailAddress: "[email protected]",
leadingConfiguration: CPMessageListItemLeadingConfiguration(
leadingItem: .image(UIImage(named: "jane_photo")!),
leadingImage: UIImage(named: "jane_photo")!,
unreadIndicator: false
),
trailingConfiguration: trailingConfig,
detailText: "Thanks for the update!",
trailingText: "Yesterday"
)
// Create list template with messages
let section = CPListSection(items: [message1, message2])
let listTemplate = CPListTemplate(title: "Messages", sections: [section])
interfaceController.setRootTemplate(listTemplate, animated: true)
列表图像行项目
// Create images for grid display
let image1 = UIImage(named: "album_cover_1")!
let image2 = UIImage(named: "album_cover_2")!
let image3 = UIImage(named: "album_cover_3")!
let image4 = UIImage(named: "album_cover_4")!
// Create list image row item with titles
let imageRowItem = CPListImageRowItem(
text: "Recently Played",
images: [image1, image2, image3, image4],
imageTitles: ["Rock Hits", "Chill Vibes", "Workout Mix", "Road Trip"]
)
// Handle image selection
imageRowItem.listImageRowHandler = { item, index, completion in
// User selected image at index
print("Selected image \(index)")
self.playAlbum(at: index)
completion()
}
// Handle row selection
imageRowItem.handler = { item, completion in
// User tapped the entire row (not a specific image)
self.showRecentlyPlayed()
completion()
}
// Create list with image row
let section = CPListSection(items: [imageRowItem])
let listTemplate = CPListTemplate(title: "Music", sections: [section])
// Update images dynamically
let newImages = [image1, image2, image3, image4, image5]
imageRowItem.update(newImages)
CarPlay 中的通知
import UserNotifications
// Request CarPlay notification permission
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .carPlay]
) { granted, error in
// Handle result
}
// Create a category that appears in CarPlay
let carPlayCategory = UNNotificationCategory(
identifier: "DELIVERY_UPDATE",
actions: [],
intentIdentifiers: [],
options: [.allowInCarPlay]
)
UNUserNotificationCenter.current().setNotificationCategories([carPlayCategory])
导航多点触控(iOS 26+)
// Multitouch is automatically enabled on iOS 26+
// Handle zoom changes in your map delegate
extension CarPlaySceneDelegate: CPMapTemplateDelegate {
func mapTemplate(_ mapTemplate: CPMapTemplate,
panWith direction: CPMapTemplate.PanDirection) {
// Update map viewport for pan gestures
updateMapViewport(direction: direction)
}
func mapTemplateDidBeginPanGesture(_ mapTemplate: CPMapTemplate) {
// User started panning — pause auto-follow
}
func mapTemplate(_ mapTemplate: CPMapTemplate,
didEndPanGestureWithVelocity velocity: CGPoint) {
// Pan ended — optionally resume auto-follow
}
}
导航语音提示
import AVFoundation
class NavigationVoiceManager {
private let synthesizer = AVSpeechSynthesizer()
func speak(_ instruction: String) {
let session = AVAudioSession.sharedInstance()
try? session.setCategory(
.playback,
mode: .voicePrompt,
options: [.duckOthers, .interruptSpokenAudioAndMixWithOthers]
)
try? session.setActive(true)
let utterance = AVSpeechUtterance(string: instruction)
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
synthesizer.speak(utterance)
}
func stop() {
synthesizer.stopSpeaking(at: .immediate)
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
}
}
导航重新规划路线(iOS 17.4+)
// When the driver deviates from the planned route
func handleReroute(session: CPNavigationSession, trip: CPTrip) {
// Calculate new route
calculateNewRoute(from: currentLocation, to: trip.destination) { newRouteChoice in
// Resume trip with new route
session.resumeTrip(for: trip, with: newRouteChoice)
// Update maneuvers for the new route
session.upcomingManeuvers = self.buildManeuvers(for: newRouteChoice)
}
}
仪表盘 / HUD 导航元数据(iOS 17.4+)
// Enable metadata forwarding to vehicle HUD and instrument cluster
extension CarPlaySceneDelegate: CPMapTemplateDelegate {
func mapTemplateShouldProvideNavigationMetadata(
_ mapTemplate: CPMapTemplate
) -> Bool {
// Return true to forward maneuver data to vehicle displays
return true
}
}
// Provide route information for the instrument cluster
func updateInstrumentCluster(
controller: CPInstrumentClusterController,
maneuvers: [CPManeuver],
laneGuidances: [CPLaneGuidance],
trip: CPTrip,
estimates: [CPManeuver: CPTravelEstimates]
) {
let routeInfo = CPRouteInformation(
maneuvers: maneuvers,
laneGuidances: laneGuidances,
currentManeuvers: [maneuvers.first].compactMap { $0 },
currentLaneGuidance: laneGuidances.first,
trip: trip,
maneuverTravelEstimates: estimates
)
// Route info is automatically forwarded when metadata is enabled
}
CarPlay 中的小组件(iOS 26+)
import WidgetKit
import SwiftUI
struct MyCarPlayWidget: Widget {
let kind: String = "CarPlayWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
CarPlayWidgetView(entry: entry)
}
.configurationDisplayName("Quick Status")
.description("Shows status on CarPlay home screen.")
.supportedFamilies([.accessoryCircular, .accessoryRectangular])
}
}
struct CarPlayWidgetView: View {
var entry: Provider.Entry
var body: some View {
VStack {
Image(systemName: "car.fill")
Text(entry.statusText)
.font(.caption)
}
}
}
CarPlay 中的实时活动(iOS 26+)
import ActivityKit
struct DeliveryAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var status: String
var eta: Date
}
var orderNumber: String
}
// Configure activity to appear in CarPlay
let content = ActivityContent(
state: DeliveryAttributes.ContentState(
status: "On the way",
eta: Date().addingTimeInterval(600)
),
staleDate: nil
)
let activity = try Activity<DeliveryAttributes>.request(
attributes: DeliveryAttributes(orderNumber: "12345"),
content: content,
pushType: .token
)
// In your widget configuration, add CarPlay support:
// .supplementalActivityFamilies([.carPlay])
启动其他应用
// Open another app from CarPlay (e.g., hand off to phone for payment)
func openCompanionApp(scene: CPTemplateApplicationScene) {
guard let url = URL(string: "myapp://complete-order") else { return }
scene.open(url, options: nil) { success in
if success {
print("Opened companion app")
}
}
}
参考文档
如需完整的 API 详情,请参阅 references/ 目录中的分类文档。