carplay

carplay

热门

基于 CarPlay 框架开发支持 CarPlay 的车机应用。适用于为车载屏幕构建导航、音频、通讯、电动车充电、停车或快捷点餐类应用,使用 CPTemplateApplicationScene、CPInterfaceController 模板层级结构、CPListTemplate、CPMapTemplate、CPNowPlayingTemplate,配置 CarPlay 专项权限(Entitlements),以及整合 CarPlay 模拟器进行测试等场景。

967Star
49Fork
更新于 2026/7/31
SKILL.md
只读
名称
carplay
描述

基于 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)、CPTemplateApplicationSceneCPTemplateApplicationSceneDelegateCPInterfaceController 和系统级 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 快捷点餐

项目配置步骤

  1. 在开发者中心(Developer Portal)的 Additional Capabilities 下更新 App ID。
  2. 为更新后的 App ID 生成全新的 Provisioning Profile(描述文件)。
  3. 在 Xcode 中禁用自动签名(Automatic Signing),并导入 CarPlay 描述文件。
  4. 添加 Entitlements.plist 并将相应的权限 Key 设置为 true
  5. 将 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