mcpField
C
carplay

carplay

github.com/ios-agent开发1277 次安装35 周前更新
代码仓库

关于 carplay

carplay 是面向 iOS CarPlay 开发者的参考技能,涵盖十类应用及相关框架、模板和界面能力。内容还涉及权限要求、通知、导航、车载组件与模拟器测试。

主要功能

  1. 01覆盖十类 CarPlay 应用
  2. 02提供模板、界面与导航 API 参考
  3. 03说明各类别权限与模板限制
  4. 04涵盖通知、组件和实时活动
  5. 05提供 CarPlay Simulator 测试指引

适用场景

  1. 01开发 CarPlay 音频、播客或音乐应用
  2. 02实现导航、通信、停车或充电应用
  3. 03为 CarPlay 添加通知、Widget 或 Live Activity
  4. 04使用模拟器测试多种车载屏幕尺寸

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ios-agent/iosagent.dev --skill carplay

安装后在 Claude Code 中输入/carplay确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
api_reference.md
audio.md
communication.md
dashboard.md
developer_guide.md
getting_started.md
index.md
navigation.md
poi.md
templates.md
ui_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 参考
  • CPInterfaceController
  • CPWindow
  • CarPlay
通信
  • CPContact
  • CPMessageListItem
入门
  • CPSessionConfiguration
  • CPTemplateApplicationScene
  • CPTemplateApplicationSceneDelegate
  • CPTemplateApplicationScene.open(_:options:completionHandler:)
导航
  • CPManeuver
  • CPNavigationSession
  • CPRouteChoice
  • CPRouteInformation
  • CPTrip
  • CPManeuverType
  • CPJunctionType
  • CPTrafficSide
  • CPLaneStatus
兴趣点
  • CPPointOfInterest
模板
  • CPActionSheetTemplate
  • CPAlertTemplate
  • CPContactTemplate
  • CPGridTemplate
  • CPInformationTemplate
  • CPListTemplate
  • CPMapTemplate
  • CPNowPlayingTemplate
  • CPPointOfInterestTemplate
  • CPSearchTemplate
  • CPTabBarTemplate
  • CPTemplate
  • CPVoiceControlTemplate
UI 元素
  • CPAlertAction
  • CPBarButton
  • CPButton
  • CPGridButton
  • CPInformationItem
  • CPListImageRowItem
  • CPListItem
  • CPListSection
  • CPTextButton
仪表盘(iOS 13.4+)
  • CPDashboardButton
  • CPDashboardController
  • CPTemplateApplicationDashboardScene
  • CPTemplateApplicationDashboardSceneDelegate
仪表盘(iOS 15.4+)
  • CPInstrumentClusterController
  • CPTemplateApplicationInstrumentClusterScene
  • CPTemplateApplicationInstrumentClusterSceneDelegate
通知
  • 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。

如何申请授权:

  1. 访问 https://developer.apple.com/contact/carplay/
  2. 说明应用的功能和类别
  3. 等待 Apple 审核并批准(通常需要 1-2 周)
  4. 将获批的授权添加到 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。这适用于跳转到基于手机的流程(例如完成购买)。系统会在切换前提示用户。

使用指南

  1. 使用适合 App 类别的模板
  2. 保持内容简洁 - 为保障驾驶安全,限制项目数量和文本
  3. 提供清晰的大尺寸图像,以提高可见性
  4. 妥善处理连接和断开连接
  5. 先在 CarPlay 模拟器中测试,再在车辆中测试
  6. 遵守减少分心的指南 - 驾驶时不得播放视频、动画或广告
使用 CarPlay 模拟器进行测试

测试 CarPlay 有两种方式:

选项 1:Xcode 模拟器窗口

  1. 在 Xcode 中打开 CarPlay 应用项目
  2. 选择一个 iOS 模拟器作为运行目标
  3. 在模拟器菜单中选择:I/O > External Displays > CarPlay
  4. CarPlay 显示窗口会与 iOS 模拟器一同出现

选项 2:独立 CarPlay 模拟器(Mac 应用)

  1. 从 developer.apple.com 下载 Additional Tools for Xcode
  2. 安装 CarPlay Simulator 应用
  3. 将实体 iPhone 连接到 Mac
  4. 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/ 目录中的分类文档。

评论

支持 Markdown