mcpField
C
carplay

carplay

github.com/dpearson2699开发1.2k3.1k 次安装13 周前更新
代码仓库

关于 carplay

面向 Swift 开发者的 CarPlay 开发技能,介绍如何构建按类别授权、基于系统模板的车载应用,并涵盖场景配置、导航、音频、通信和地点信息界面。

主要功能

  1. 01配置 CarPlay 类别授权
  2. 02设置 CarPlay 场景与代理
  3. 03使用系统模板组织界面
  4. 04构建导航、音频和通信体验
  5. 05展示兴趣点及地点详情
  6. 06通过 CarPlay 模拟器测试

适用场景

  1. 01开发车载导航应用
  2. 02呈现音频浏览和播放界面
  3. 03构建车载消息与联系人界面
  4. 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快速订餐
项目配置
  1. 在开发者门户的“其他功能”中更新 App ID。
  2. 为更新后的 App ID 生成新的配置描述文件。
  3. 在 Xcode 中关闭自动签名,并导入 CarPlay 配置描述文件。
  4. 添加 Entitlements.plist,并将授权密钥设为 true。
  5. 将“代码签名授权”构建设置设为 Entitlements.plist 的路径。
关键类型
类型作用
CPTemplateApplicationScene用于 CarPlay 显示屏的 UIScene 子类
CPTemplateApplicationSceneDelegate场景连接和断开的生命周期管理
CPInterfaceControllerCarPlay 提供的控制器,用于设置根模板,以及推入、呈现或弹出模板
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 模拟器测试

  1. 在 Xcode 中使用 iOS 模拟器构建并运行。
  2. 选择“输入/输出” >“外接显示器” >“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 锁定时应用仍能正常运行
  • 在最低、标准和高分辨率模拟器尺寸下进行测试
  • 不主动播放时停用音频会话

参考资料

评论

支持 Markdown