WWDC Quick Look 💓 By SwiftGGTeam
What's new in CarPlay

What's new in CarPlay

观看原视频

Highlight

iOS 26 让 widget 与 Live Activity 无需 CarPlay App 即可上车,导航 App 可通过 54 种 maneuver 类型把转向元数据直接投到仪表盘与 HUD。


核心内容

CarPlay 过去对开发者是一个高门槛入口:必须申请音频、通讯、导航等几类 entitlement,才能把 App 摆上车机屏幕。绝大多数 App 既不属于这些品类,也没有动力专门写一个 CarPlay 版本,结果就是车里那块屏幕和你的产品没有关系。iOS 26 把这堵墙拆了——只要 App 已经实现了 systemSmall 尺寸的 widget,或者已经在用 Live Activities,就能自动出现在 CarPlay Dashboard 上,不需要 CarPlay App,也不需要 entitlement。

司机会向左滑动 Dashboard 看到 widget;触屏车型还能直接交互(02:27)。Live Activity 则在 iPhone 端启动后自动进入 Dashboard,如果 Dashboard 没显示,CarPlay 会把它作为通知贴在屏幕底部,避免错过重要进度(05:23)。CarPlay 用 activity family small 这个尺寸来渲染——如果你还没实现,会回退到 Dynamic Island 的 compact leading/trailing。对外卖、打车、航班这类应用,等于免费拿到了一块车载展示位。

第二条线是给已有 CarPlay App 的能力升级:列表模板新增 pinned headerGridButtons、Now Playing 模板支持体育赛事模式(双队伍 + 比分 + 比赛时钟)、地图模板拿到多点触控回调(pinch/pitch/rotate)。最重的一块是导航元数据——通过 CPNavigationSession 上报 54 种 maneuver 类型和 junction 角度,车辆负责在仪表盘和 HUD 上用自己的设计语言渲染,App 完全不用关心具体外观。


详细内容

widget 上车的最小动作:实现 systemSmall family 即可。如果 widget 不适合车内场景(高密度文字、需要密集交互、依赖数据保护 class A/B),用 disfavoredLocations 显式声明(03:21):

// Disfavored locations modifier for CarPlay

WidgetConfiguration()
    .disfavoredLocations([.carPlay], for: [.systemSmall])

关键点

  • .disfavoredLocations([.carPlay], for: [.systemSmall]) 告诉系统:这个 widget 不适合 CarPlay。
  • 设置后 widget 仍会出现在 CarPlay 设置里,但会被分组并标注”未优化”。
  • 司机若坚持启用,widget 的交互能力会被禁用,只显示静态内容。
  • 适合声明为 disfavored 的场景:游戏类 widget、点击会刷新内容的 widget、主入口是无 CarPlay 版 App 的 widget。

列表模板的 pinned 元素:列表顶部现在可以钉一行 grid buttons(10:05):

// Pinned elements

var headerGridButtons: [CPGridButton]?

// Create a Grid Button

class CPGridButton

init(titleVariants: [String],
     image: UIImage,
     handler: ((CPGridButton) -> Void)?)

关键点

  • headerGridButtonsCPListTemplate 上的新属性,赋值后会渲染在第一个 section 之上。
  • titleVariants 是字符串数组——系统会根据可用宽度自动选最合适的版本。
  • handler 闭包在按钮被点击时调用。
  • 通讯类 App 还可以通过 CPMessageGridItemConfiguration(conversationIdentifier:unread:) 传 SiriKit 会话标识和未读标记。

导航元数据:导航 App 通过实现一个 delegate 方法声明支持,然后把 maneuver 和 lane guidance 提前喂给 CPNavigationSession16:28):

// Add support for metadata

// Declare support

func mapTemplateShouldProvideNavigationMetadata(_ mapTemplate: CPMapTemplate) -> Bool {
    true
}

// Provide maneuver information up-front

cpNavigationSession.add(maneuvers)
cpNavigationSession.add(laneGuidance)

// Reroute

cpNavigationSession.pauseTrip(for: .rerouting, description: "Rerouting")
cpNavigationSession.resumeTrip(updatedRouteInformation: cpRouteInformation)

关键点

  • mapTemplateShouldProvideNavigationMetadata 返回 true 是元数据通道的开关,系统据此决定是否向车辆请求转向信息。
  • cpNavigationSession.add(maneuvers) 一次性把后续 maneuver 推送出去,避免逐个传输带来的延迟——CPManeuver 支持 54 种类型,覆盖直行、左右转、入/出匝道、环岛、上下渡轮等场景(15:53)。
  • add(laneGuidance) 提交车道指引数据。
  • 重新规划路径时用 pauseTrip(for: .rerouting, description:) 暂停当前行程,构造新的 CPRouteInformation 后调用 resumeTrip(updatedRouteInformation:) 切换。
  • 车辆负责渲染——App 只提供语义类型(如 straightAheadonRampoffRampenter_ferryarriveAtDestination),实际外观由仪表盘和 HUD 自行决定(16:16)。

测试通路:不需要真车,下载 Additional Tools for Xcode 中的 CarPlay Simulator,用 USB 把 iPhone 连到 Mac 就能复现完整体验(06:25)。


核心启发

  • 做什么:把已有的 systemSmall widget 适配到 CarPlay。

    • 为什么值得做:成本极低——可能只需要加一行 containerBackgroundRemovable 和系统字体检查,就能拿到 CarPlay Dashboard 一个展示位。对非 CarPlay 品类的 App 而言,这是唯一一次进入车机的机会。
    • 怎么开始:用 CarPlay Simulator 跑现有 widget,看默认效果;若 widget 高密度、依赖交互或锁屏数据,先用 disfavoredLocations([.carPlay], for: [.systemSmall]) 声明,避免被司机误启用后体验崩坏。
  • 做什么:给 Live Activity 实现 activity family small

    • 为什么值得做:watchOS Smart Stack 与 CarPlay 共用同一个尺寸类,做一次适配,两端都得到精心设计的展示,而不是从 Dynamic Island compact 退化回来的凑合版。
    • 怎么开始:审视当前 Live Activity 的状态机,只保留”最重要的几个状态”在 small 尺寸里展示——CarPlay 上是非交互的,要假设司机只瞥一眼。
  • 做什么:导航 App 接入 navigation metadata。

    • 为什么值得做:仪表盘和 HUD 是司机视线最近的位置,把转向投到那里是用户体验的代际差距,而且 CarPlay 帮你处理了车辆通信和状态管理。
    • 怎么开始:实现 mapTemplateShouldProvideNavigationMetadata 返回 true,把 maneuver 类型映射到 Apple 的 54 种枚举,提前 cpNavigationSession.add(maneuvers) 批量上报,最后用 CarPlay Simulator 验证。
  • 做什么:音频 App 检查 audio session 生命周期。

    • 为什么值得做:CarPlay 环境同时存在 iPhone 音源、车载 FM/卫星广播、其他 CarPlay App 的音频。session 没及时 deactivate,会无故抢占其他音源,体验非常差(13:07)。
    • 怎么开始:搜索代码里所有 AVAudioSession.setActive(true),确认每个都有对应的 setActive(false),并把激活时机收窄到”即将播放音频”的那一刻。

关联 Session

评论

GitHub Issues · utterances