WWDC Quick Look 💓 By SwiftGGTeam
Get more mileage out of your app with CarPlay

Get more mileage out of your app with CarPlay

观看原视频

Highlight

CarPlay 的设计哲学一直是「为驾驶者服务」。你的 app 在 CarPlay 上应该只提供驾驶时真正需要的功能,登录、配置、阅读条款这些事留给 iPhone。


核心内容

CarPlay app 的难点是克制。车上的用户正在驾驶,界面必须少、任务必须短、信息必须一眼看完。

00:55)Apple 在开场给了一个很明确的边界:CarPlay 是为驾驶者设计的。一次性配置、登录、阅读条款,都应该放在驾驶前或驾驶后完成,不应该出现在 CarPlay UI 里。

这条边界决定了后面的所有设计。

01:59)iOS 16 新增两类 CarPlay app:Fueling 和 Driving Task。前者把 iOS 14 的 EV Charging 思路扩展到加油和替代燃料场景;后者给极简单的驾驶任务一个正式入口。

09:16)Apple 还发布了 CarPlay Simulator。它是 Mac 上的独立应用,连接 iPhone 后可以在桌面复现真实 CarPlay 环境。开发者可以同时使用 Xcode、Instruments,并测试不同屏幕尺寸、外观、断连重连和仪表盘显示。

14:53)导航 app 还有一个新能力:在支持的车辆上,把实时地图或转向卡片显示到数字仪表盘(instrument cluster)。如果 app 已经支持 CarPlay Dashboard,这个改造沿用同一套模式:在 Info.plist 里声明 scene,随后实现对应 delegate。


详细内容

CarPlay 模板替你控制复杂度

02:14)CarPlay 的 UI 由模板(template)组成。app 提供数据,系统负责把 UI 绘制到车载屏幕上。

这件事有三个直接收益。

第一,开发者不需要自己处理字体大小和车载输入设备差异。第二,模板会把 UI 复杂度压低。第三,同一类模板在不同 CarPlay app 中保持一致,驾驶者能更快完成操作。

本 session 没有给出模板 API 的官方代码片段,但明确列出了几个模板的使用场景:

// 概念示例:根据 session 中提到的模板选择 UI 形态
let roadStatusTemplate = CPPointOfInterestTemplate(...)
let accessoryTemplate = CPInformationTemplate(...)
let quickChoiceTemplate = CPGridTemplate(...)

关键点:

  • CPPointOfInterestTemplate:session 用它举例道路状态 app,适合展示用户附近的少量重要地点。
  • CPInformationTemplate:session 用它举例拖车控制器,适合展示一个配件状态和少量操作按钮。
  • CPGridTemplate:session 用它举例里程记录和快速车道收费,适合一两个快速选择按钮。

这里的重点是模板限制。不同 app 类型只能使用相关且适合驾驶场景的模板,Apple 在开发者文档中提供了对应表格。

Fueling app 要做加油过程,不只是找地点

04:26)iOS 16 新增 Fueling app 类型。它来自 iOS 14 的 EV Charging app 思路,但对象扩展到传统燃油车和替代燃料车辆。

Apple 特别提醒:很多用户已经会用导航 app 查找并驶向某个地点。所以 Fueling app 在 CarPlay 上只做“找加油站”并不够。

更合适的功能是驾驶时真正需要的加油流程,例如连接到正确的加油站设备,或直接启动加油泵。

// 概念示例:Fueling app 的 CarPlay UI 应该围绕驾驶时动作
struct FuelingAction {
    let stationName: String
    let pumpIdentifier: String
    let primaryActionTitle = "Start Pump"
}

关键点:

  • stationName:CarPlay UI 只需要展示当前相关的地点,不应该做完整地点规划。
  • pumpIdentifier:session 明确提到 app 可以帮助用户连接到正确设备。
  • primaryActionTitle:核心动作应该是启动加油泵这类驾驶中需要完成的任务。

这个示例是概念模型,不是 Apple 提供的新 API。官方 transcript 只说明产品边界和场景,没有给出 Fueling 类型的代码片段。

Driving Task app 应该短到几秒完成

05:22)Driving Task 是新的 CarPlay app 类型,用来承载更广泛的简单驾驶任务。

Apple 给了四类例子:道路状态信息、车辆配件控制、行程开始或结束时的任务、快速车道收费人数选择。这些任务的共同点是小,最好单屏完成。

08:40)设计规则很硬:考虑做成单屏 app,只提供驾驶时最小必要功能,只允许几秒内完成的任务,避免复杂或低频用例。

// 概念示例:Driving Task 的操作集合应该很小
let mileageActions = [
    CPGridButton(titleVariants: ["Personal"], image: personalImage) { _ in
        recordPersonalMileage()
    },
    CPGridButton(titleVariants: ["Business"], image: businessImage) { _ in
        recordBusinessMileage()
    }
]

关键点:

  • mileageActions:session 中的里程记录 app 只有两个按钮,分别记录个人或商务里程。
  • CPGridButton:session 说明这类 app 可以使用 CPGridTemplate,按钮式选择适合几秒内完成。
  • recordPersonalMileage() / recordBusinessMileage():示例函数只表示业务动作,session 没有提供具体实现。

这个例子保留了 transcript 中的 UI 形态,但函数名是概念性的。不能把它当作官方完整 API 示例。

CarPlay Simulator 让真机调试回到桌面

10:18)CarPlay Simulator 是一个独立 Mac 应用。下载 Additional Tools for Xcode,运行应用,用线缆把 iPhone 连到 Mac,CarPlay 就会像连上真实汽车一样启动。

它解决的是测试成本。

以前要在真实 iPhone 上测试 CarPlay UI,通常需要真实车辆或 aftermarket head unit。现在可以坐在桌前调试真机 app,同时用 Xcode 断点和 Instruments 做性能分析。

12:38)Simulator 还提供多种车辆行为开关:限制 UI、切换深浅色外观、模拟断开和重连、调整主显示屏尺寸、启用仪表盘第二屏。

CarPlay Simulator 测试清单:
1. 切换 light / dark appearance,检查图像资源。
2. 点击 Limit UI,确认行驶中内容会被适当缩短。
3. 改变 main display size,检查模板和地图绘制。
4. 启用 instrument cluster display,检查第二屏地图或转向卡片。
5. 模拟 disconnect / reconnect,调试重连逻辑。

关键点:

  • 第 1 步:session 演示了 Express Lane app 为深浅色提供不同 artwork。
  • 第 2 步:Limit UI 按钮用于模拟车辆行驶时要求 CarPlay 限制屏幕内容。
  • 第 3 步:模板 UI 通常由系统适配,导航 app 的地图绘制代码需要重点验证。
  • 第 4 步:仪表盘第二屏主要面向导航 app。
  • 第 5 步:手机仍连接 Mac,断连重连场景可以直接用 Xcode 调试。

导航 app 接入数字仪表盘

15:07)数字仪表盘支持沿用 iOS 13 CarPlay Dashboard 的模式。先在 Info.plist 声明支持,再添加对应 Scene Session Role。

官方代码片段如下:

<key>UIApplicationSceneManifest</key>
<dict>
    <!-- Indicate support for CarPlay dashboard -->
    <key>CPSupportsDashboardNavigationScene</key>
    <true/>
    <!-- Indicate support for instrument cluster displays -->
    <key>CPSupportsInstrumentClusterNavigationScene</key>
    <true/>
    <!-- Indicate support for multiple scenes -->
    <key>UIApplicationSupportsMultipleScenes</key>
    <true/>
    <key>UISceneConfigurations</key>
    <dict>
        <!-- For device scenes -->
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string>UIWindowScene</string>
                <key>UISceneConfigurationName</key>
                <string>Phone</string>
                <key>UISceneDelegateClassName</key>
                <string>MyAppWindowSceneDelegate</string>
            </dict>
        </array>
        <!-- For the main CarPlay scene -->
        <key>CPTemplateApplicationSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string>CPTemplateApplicationScene</string>
                <key>UISceneConfigurationName</key>
                <string>CarPlay</string>
                <key>UISceneDelegateClassName</key>
                <string>MyAppCarPlaySceneDelegate</string>
            </dict>
        </array>
        <!-- For the CarPlay Dashboard scene -->
        <key>CPTemplateApplicationDashboardSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string>CPTemplateApplicationDashboardScene</string>
                <key>UISceneConfigurationName</key>
                <string>CarPlay-Dashboard</string>
                <key>UISceneDelegateClassName</key>
                <string>MyAppCarPlayDashboardSceneDelegate</string>
            </dict>
        </array>
        <!-- For the CarPlay instrument cluster scene -->
        <key>CPTemplateApplicationInstrumentClusterSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string>CPTemplateApplicationInstrumentClusterScene</string>
                <key>UISceneConfigurationName</key>
                <string>CarPlay-Instrument-Cluster</string>
                <key>UISceneDelegateClassName</key>
                <string>MyAppCarPlayInstrumentClusterSceneDelegate</string>
            </dict>
        </array>
    </dict>
</dict>

关键点:

  • CPSupportsDashboardNavigationScene:声明 app 支持 CarPlay Dashboard 导航 scene。
  • CPSupportsInstrumentClusterNavigationScene:声明 app 支持数字仪表盘导航 scene。
  • UIApplicationSupportsMultipleScenes:允许 app 同时管理多个 scene。
  • CPTemplateApplicationSceneSessionRoleApplication:主 CarPlay scene,用于车机主屏。
  • CPTemplateApplicationDashboardSceneSessionRoleApplication:Dashboard scene,用于 CarPlay Dashboard。
  • CPTemplateApplicationInstrumentClusterSceneSessionRoleApplication:数字仪表盘 scene,用于仪表盘地图或转向卡片。
  • UISceneDelegateClassName:为每个 scene 绑定对应 delegate,分别处理 phone、CarPlay、Dashboard 和 instrument cluster。

16:00)声明 scene 之后,还要实现仪表盘相关 delegate。官方片段如下:

extension TemplateApplicationSceneDelegate: CPTemplateApplicationInstrumentClusterSceneDelegate {
    
    func templateApplicationInstrumentClusterScene(
        _ templateApplicationInstrumentClusterScene: CPTemplateApplicationInstrumentClusterScene,
        didConnect instrumentClusterController: CPInstrumentClusterController) {
        // Connected to Instrument Cluster
        TemplateManager.shared.clusterController(instrumentClusterController, didConnectWith: templateApplicationInstrumentClusterScene.contentStyle)
    }
    


    func instrumentClusterControllerDidConnect(_ instrumentClusterWindow: UIWindow) {
        // Window in which to draw instrument cluster contents 
       self.instrumentClusterWindow = instrumentClusterWindow
    }

关键点:

  • CPTemplateApplicationInstrumentClusterSceneDelegate:让 scene delegate 接收仪表盘 scene 的连接事件。
  • didConnect instrumentClusterController:当 CarPlay 连接到仪表盘控制器时调用。
  • CPInstrumentClusterController:用于处理仪表盘相关控制,例如 session 后面提到的缩放、罗盘、限速显示时机。
  • templateApplicationInstrumentClusterScene.contentStyle:把当前内容样式传给 app 自己的模板管理逻辑。
  • instrumentClusterControllerDidConnect(_:):系统把可绘制的 UIWindow 交给 app。
  • self.instrumentClusterWindow:app 保存这个窗口,随后在其中绘制仪表盘内容。

16:26)仪表盘还有几个额外注意点。车辆可能允许用户缩放地图,这需要通过 CPInstrumentClusterControllerDelegate 实现。罗盘或限速显示也要等 delegate 告诉 app 何时适合绘制。

16:57)仪表盘画面可能被车内其他元素遮挡。处理方式是安全区域(safe area):在 view controller 里重写 viewSafeAreaInsetsDidChange,并用 cluster view 的 safeAreaLayoutGuide 约束关键内容。

// 概念示例:把关键地图内容限制在安全区域内
final class ClusterMapViewController: UIViewController {
    override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()
        updateRouteOverlayLayout(using: view.safeAreaLayoutGuide)
    }
}

关键点:

  • viewSafeAreaInsetsDidChange():session 明确提到可重写它来获知安全区域变化。
  • view.safeAreaLayoutGuide:session 建议用它保证关键内容位于可见区域。
  • updateRouteOverlayLayout:概念函数,代表 app 自己更新路线线条、当前位置等关键地图内容。

核心启发

1. 做一个只保留驾驶动作的配件控制 CarPlay UI

  • 做什么:把拖车、车顶箱、胎压设备这类配件控制压缩成一个 CPInformationTemplate 页面。
  • 为什么值得做:session 的拖车控制器例子说明,CarPlay UI 可以只包含状态和少量按钮,配对、配置等复杂功能留在 iPhone。
  • 怎么开始:先列出驾驶中必须操作的 1-2 个动作,再用 CarPlay 模板表达;不要把 iPhone 设置页迁移过去。

2. 做一个两按钮的里程记录入口

  • 做什么:在驾驶开始或结束时,让用户用两个按钮记录 personal / business mileage。
  • 为什么值得做:Apple 用这个例子说明 Driving Task app 的理想形态:单屏、短任务、几秒完成。
  • 怎么开始:用 CPGridTemplate 组织少量按钮,按钮回调只写入当前行程分类,详细统计放回 iPhone app。

3. 给导航 app 增加仪表盘地图验证流程

  • 做什么:在 CarPlay Simulator 中启用 instrument cluster display,检查地图、转向卡片和安全区域。
  • 为什么值得做:iOS 16 允许导航 app 把地图或 turn card 放到驾驶者视线内的数字仪表盘。
  • 怎么开始:在 Info.plist 添加 CPSupportsInstrumentClusterNavigationScene 和 instrument cluster scene role,再实现 CPTemplateApplicationInstrumentClusterSceneDelegate

4. 建立 CarPlay Simulator 回归测试清单

  • 做什么:每次发布前测试深浅色、Limit UI、屏幕尺寸、断连重连和仪表盘第二屏。
  • 为什么值得做:这些都是 session 中 CarPlay Simulator 明确支持的车辆环境模拟能力。
  • 怎么开始:把 Additional Tools for Xcode 加入团队开发环境,真机连接 Mac 后逐项验证,导航 app 重点测试不同 display size 和 aspect ratio。

关联 Session

评论

GitHub Issues · utterances