其他
开发 CarPlay 模板应用(导航/音频/通信/POI/E…
在 iOS 26+ 上用 Swift 6.3 构建基于模板、带类别授权的 CarPlay 车机应用时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 38/100 ×0.15
适用场景
开发 CarPlay 模板应用(导航/音特定
解决思路
- 从 Apple 的 CarPlay entitlement 表单申请类别专用 entitlement,接受 addendum,然后按获批类别配置对应 key。
- 按类别使用 entitlement key:audio 用 `com.apple.developer.carplay-audio`;communication 用 `com.apple.developer.carplay-communication`;navigation 用 `com.apple.developer.carplay-maps`;EV Charging 用 `com.apple.developer.carplay-charging`;Parking 用 `com.apple.developer.carplay-parking`;Quick Food Ordering 用 `com.apple.developer.carplay-quick-ordering`。
- 在 developer portal 的 Additional Capabilities 下更新 App ID。
- 为更新后的 App ID 重新生成 provisioning profile。
- 在 Xcode 中关闭 automatic signing,导入 CarPlay provisioning profile。
- 添加 `Entitlements.plist`,将对应 entitlement key 设为 `true`。
- 将 Code Signing Entitlements build setting 设为 `Entitlements.plist` 路径。
- 使用核心类型:`CPTemplateApplicationScene` 作为 CarPlay 显示场景的 UIScene 子类;`CPTemplateApplicationSceneDelegate` 处理连接/断开生命周期;`CPInterfaceController` 设置 root template 并 push/present/pop template;`CPTemplate` 是所有 CarPlay template 的抽象基类;`CPSessionConfiguration` 提供车辆显示限制和内容样式。
- 在 `Info.plist` 声明 CarPlay scene:设置 `UIApplicationSceneManifest`,`UIApplicationSupportsMultipleScenes` 为 `true`;在 `UISceneConfigurations` 中添加 `CPTemplateApplicationSceneSessionRoleApplication` 数组,字典中设置 `UISceneClassName` 为 `CPTemplateApplicationScene`,`UISceneConfigurationName` 为 `CarPlaySceneConfiguration`,`UISceneDelegateClassName` 为 `$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate`。
- 实现非导航类 `CPTemplateApplicationSceneDelegate`:在 `templateApplicationScene(_:didConnect:)` 中保存传入的 `CPInterfaceController`,调用 `setRootTemplate(buildRootTemplate(), animated: true, completion: nil)`;在 `templateApplicationScene(_:didDisconnectInterfaceController:)` 中将 interfaceController 置空。
- 实现导航类 `CPTemplateApplicationSceneDelegate`:在 `templateApplicationScene(_:didConnect:to:)` 中保存 `CPInterfaceController` 和 `CPWindow`,将 `window.rootViewController` 设为 `MapViewController()`,创建 `CPMapTemplate`,设置其 `mapDelegate`,再调用 `setRootTemplate(mapTemplate, animated: true, completion: nil)`。
- 选用通用模板:`CPTabBarTemplate` 作为带标签子模板的容器;`CPListTemplate` 作为可滚动分组列表;`CPGridTemplate` 作为可点击图标网格(最多 8 个);`CPInformationTemplate` 作为 key-value 信息页(最多 3 个 action);`CPAlertTemplate` 作为模态 alert(最多 2 个 action);`CPActionSheetTemplate` 作为模态 action sheet。
- 选用类别专用模板:导航用 `CPMapTemplate`(带导航栏的地图覆盖层)和 `CPSearchTemplate`(目的地搜索);音频用 `CPNowPlayingTemplate`(共享 Now Playing 屏);EV 充电/停车/点餐用 `CPPointOfInterestTemplate`(POI 地图);通信用 `CPContactTemplate`(联系人卡片)。
- 导航层级操作:用 `pushTemplate(_:animated:completion:)` 将模板加入栈;用 `presentTemplate(_:animated:completion:)` 模态显示;用 `popTemplate(animated:completion:)` 返回;`CPTabBarTemplate` 只能设为 root,不能 push 或 present。
- 创建 `CPTabBarTemplate`:先创建子 `CPListTemplate`,给子模板设置 `tabImage`,再用 `CPTabBarTemplate(templates:)` 初始化,设置 delegate,最后通过 `setRootTemplate` 设为 root。
- 创建 `CPListTemplate`:用 `CPListItem(text:detailText:)` 创建条目,给 `item.handler` 传入 selectedItem 和 completion,在 handler 中调用 `pushTemplate(detailTemplate, animated: true, completion: nil)` 后调用 `completion()`;用 `CPListSection(items:header:sectionIndexTitle:)` 创建分组;用 `CPListTemplate(title:sections:)` 组装列表。
- 导航应用使用 `com.apple.developer.carplay-maps`;它是唯一能收到 `CPWindow` 用来绘制地图内容的类别;root template 必须是 `CPMapTemplate`。
- 实现行程预览和路线选择:创建 `CPRouteChoice(summaryVariants:additionalInformationVariants:selectionSummaryVariants:)`;创建 `CPTrip(origin:destination:routeChoices:)`;调用 `mapTemplate.showTripPreviews([trip], textConfiguration: nil)`。
- 在 `CPMapTemplateDelegate.mapTemplate(_:startedTrip:using:)` 中启动导航会话:调用 `mapTemplate.startNavigationSession(for: trip)`;用 `session.pauseTrip(for: .loading, description: "Calculating route...")` 暂停;创建 `CPManeuver` 并设置 `instructionVariants` 和 `symbolImage`;设置 `session.upcomingManeuvers`;创建 `CPTravelEstimates(distanceRemaining:timeRemaining:)`;调用 `session.updateEstimates(estimates, for: maneuver)`。
- 添加地图按钮:用 `CPMapButton { _ in ... }` 创建按钮,设置 `image`,并赋给 `mapTemplate.mapButtons`。
- 实现 `CPSearchTemplateDelegate`:在 `searchTemplate(_:updatedSearchText:completionHandler:)` 中执行搜索,将结果映射为 `CPListItem(text:detailText:)` 并传给 completionHandler;在 `searchTemplate(_:selectedResult:completionHandler:)` 中导航到选中目的地并调用 completionHandler。
- 音频应用使用 `com.apple.developer.carplay-audio`;用列表展示可浏览内容,用 `CPNowPlayingTemplate` 提供播放控制;音频授权应用不可用 `CPInformationTemplate`。
- 使用 `CPNowPlayingTemplate.shared`,它是共享单例并从 `MPNowPlayingInfoCenter` 读取元数据;不要实例化新的 `CPNowPlayingTemplate`;设置 `isUpNextButtonEnabled`、`isAlbumArtistButtonEnabled`,用 `updateNowPlayingButtons` 添加 `CPNowPlayingShuffleButton`、`CPNowPlayingRepeatButton` 等按钮,并调用 `add(self)` 注册为 `CPNowPlayingTemplateObserver`。
- 配置 Siri assistant cell:支持 `INPlayMediaIntent` 的音频应用可显示 assistant cell;通信应用用 `INStartCallIntent` 和 `.startCall`;创建 `CPAssistantCellConfiguration(position: .top, visibility: .always, assistantAction: .playMedia)`,并将它作为 `CPListTemplate(title:sections:assistantCellConfiguration:)` 的 assistantCellConfiguration 传入。
- 通信应用使用 `com.apple.developer.carplay-communication`;展示消息列表和联系人,并支持 Siri 发起的 `INStartCallIntent`;`CPMessageListItem` 没有 app 提供的 selection handler,选中时 CarPlay 根据 phone/email、unread 状态或已有 conversation 配置调用 Siri compose、read 或 reply 行为。
- 创建 `CPMessageListItem`:用 `CPMessageListItemLeadingConfiguration(leadingItem: .star, leadingImage: nil, unread: true)` 创建 leading;用 `CPMessageListItemTrailingConfiguration(trailingItem: .none, trailingImage: nil)` 创建 trailing;用 `CPMessageListItem(conversationIdentifier:text:leadingConfiguration:trailingConfiguration:detailText:trailingText:)` 创建条目;放入 `CPListSection` 后用 `CPListTemplate(title:sections:)` 展示。
- EV 充电、停车、点餐应用用 `CPPointOfInterestTemplate` 和 `CPInformationTemplate` 展示位置和详情;`CPPointOfInterestTemplate` 最多显示 12 个 POI。
- 创建 `CPPointOfInterest`:用 `MKMapItem(placemark: MKPlacemark(coordinate:))` 提供 location,设置 title、subtitle、summary、detailTitle、detailSubtitle、detailSummary、pinImage;给 `poi.primaryButton` 设置 `CPTextButton(title:textStyle:handler:)`;用 `CPPointOfInterestTemplate(title:pointsOfInterest:selectedIndex:)` 创建模板并设置 `pointOfInterestDelegate`。
- 创建 `CPInformationTemplate`:传入 title、layout(如 `.leading`)、`CPInformationItem(title:detail:)` 数组和 `CPTextButton` actions;例如 Place Order 用 `.confirm` 调用下单,Cancel 用 `.cancel` 调用 `popTemplate(animated: true, completion: nil)`。
- 用 CarPlay Simulator 测试:在 Xcode 中构建并在 iOS simulator 运行。
- 选择 I/O > External Displays > CarPlay。
- 默认窗口为 800x480 `@2x`;导航应用需要额外选项时执行 `defaults write com.apple.iphonesimulator CarPlayExtraOptions -bool YES`。
- 按推荐配置测试:Minimum 748 x 456 `@2x`;Portrait 768 x 1024 `@2x`;Standard 800 x 480 `@2x`;High-resolution 1920 x 720 `@3x`。
- 注意 Simulator 不能测试锁 iPhone 行为、Siri、与车机收音机的音频共存、物理输入硬件(旋钮、触控板);尽可能在真实 CarPlay 车辆或后装车机上测试;设计主要 CarPlay 流程时避免在 CarPlay 激活时要求 iPhone 输入。
- 使用正确的 scene delegate 方法:导航应用必须实现带 `CPWindow` 的 `templateApplicationScene(_:didConnect:to:)`;非导航应用实现无 window 的 `templateApplicationScene(_:didConnect:)`;用错会产生没有 CarPlay UI。
- 不要在导航 window 中绘制自定义 UI;`CPWindow` 只用于地图内容;所有覆盖层、alert 和控件都必须使用 CarPlay templates。
- 不要 push 或 present `CPTabBarTemplate`;它只能 root;用 `setRootTemplate(_:animated:completion:)`。
- 不要实例化 `CPNowPlayingTemplate`;用 `CPNowPlayingTemplate.shared`,否则会出问题。
- 不要给 `CPMessageListItem` 添加 handler;它由 Siri 管理,不同于 `CPListItem`;不要设置 `message.handler`,用 item 配置和 `userInfo` 传递上下文。
- 不要把 widgets 当作 CarPlay template app;CarPlay 可见的 widgets 和 Live Activities 属于 WidgetKit 和 ActivityKit;本技能只用于类别授权的 CarPlay template app scenes 以及验证这些 surface 在车机上下文中的表现。
- 不要忽略车辆显示限制;检查 `CPSessionConfiguration.limitedUserInterfaces`,并遵守 list templates 上的 `maximumItemCount` / `maximumSectionCount`。
- 不要忘记调用 completion handler;`CPListItem.handler` 的每条代码路径都必须调用 completion,否则列表会一直处于 loading 状态。
- 复核 `Entitlements.plist` 中的 CarPlay entitlement key 是否正确。
- 复核 `UIApplicationSupportsMultipleScenes` 是否设为 `true`。
- 复核 `Info.plist` 中是否有 `CPTemplateApplicationSceneSessionRoleApplication` scene。
- 复核 scene delegate class name 是否匹配 `UISceneDelegateClassName`。
- 复核是否使用了正确的 delegate 方法(带/不带 `CPWindow`)。
- 复核是否在 `didConnect` 返回前设置 root template。
- 复核 disconnect 时是否清空 interface controller 和 window 引用。
- 复核 `CPTabBarTemplate` 是否只作为 root,从未被 push。
- 复核是否使用 `CPNowPlayingTemplate.shared` 而非新实例。
- 复核 communication rows 是否使用 `CPMessageListItem` 且没有自定义 handler。
- 复核 WidgetKit/ActivityKit surface 是否路由到 CarPlay template app 代码之外。
- 复核在填充列表前是否检查 `maximumItemCount` / `maximumSectionCount`。
- 复核 `CPListItem.handler` 是否在每条路径调用 completion。
- 复核导航应用的 `CPWindow` root view controller 是否只放地图内容。
- 复核 App 在 iPhone 锁定时是否仍能工作。
- 复核是否在 minimum、standard、high-resolution simulator 尺寸下测试过。
- 复核不主动播放时是否停用 audio session。
- 查阅 `references/carplay-patterns.md` 获取完整导航会话、dashboard scenes、advanced template composition 等扩展模式,并参考 CarPlay framework、CPTemplateApplicationSceneDelegate、CPInterfaceController、CPMapTemplate、CPListTemplate、CPNowPlayingTemplate、CPPointOfInterestTemplate、CPNavigationSession、Requesting CarPlay Entitlements、Displaying Content in CarPlay、Using the CarPlay Simulator、CarPlay HIG 等文档。
适用边界 · 注意事项
- 不适用于没有 Apple 批准 CarPlay category entitlement 或没有 Apple Developer 账号/付费 provisioning 的场景。
- 不适用于仅做 CarPlay 可见 WidgetKit widget 或 ActivityKit Live Activity;这些应走 WidgetKit/ActivityKit 实现。
- CarPlay Simulator 不能验证锁 iPhone、Siri、车机收音机音频共存、物理旋钮/触控板;这些需要真车或后装主机,不能只依赖模拟器。