将 Live Activity 带到 Apple Watch
Bring your Live Activity to Apple Watch
2024年6月10日
一句话判断
watchOS 11 自动将你的 iOS Live Activity 投射到 Apple Watch Smart Stack,你只需做最少的工作就能让它在手腕上看起来很好——但如果你愿意花时间定制,体验会截然不同。
这场 Session 讲了什么
iOS 18 和 watchOS 11 带来了一个让人兴奋的变化:你在 iPhone 上创建的 Live Activity,会自动出现在 Apple Watch 的 Smart Stack 中。不需要单独开发 watchOS 版本,不需要管理额外的 push token,系统会自动同步更新。
默认情况下,Smart Stack 展示的是你 Live Activity 的 Dynamic Island compact leading 和 compact trailing 两个视图。这意味着如果你之前对 compact 视图投入过精力,现在它们在手表上也能派上用场了。但问题在于,这些视图是为 iPhone Dynamic Island 设计的,直接搬到手表上未必是最佳体验。
好消息是 Apple 提供了一套定制机制。通过 supplementalActivityFamilies 修饰符,你可以为 Smart Stack 提供专门的视图,利用 activityFamily 环境值来区分 iPhone Lock Screen 和 Apple Watch 两种场景,分别给出最合适的布局。
值得深挖的点
自动同步机制与更新预算
Live Activity 的更新从 iPhone 同步到 Apple Watch 是全自动的,这是整个方案最优雅的部分。你不需要为 watchOS 单独维护一套 push 通道,也不需要处理跨设备的状态同步。但自动同步不意味着无限制同步——Apple Watch 有自己的更新预算,阈值和 iOS 侧类似。
这里有一个容易踩坑的地方:如果你在 iOS 端用 ActivityKit 做了大量本地更新(比如频繁刷新外卖配送进度),这些更新会同步到手表并消耗手表侧的预算。超出预算后,手腕放下时可能不会立即显示最新状态,但抬腕时仍然会刷新。对于推送更新已经做得很好的 app 来说,这个预算基本够用,但本地高频更新的场景需要留意。
Always On Display 适配
Apple Watch 在 Always On 模式下会自动切换到深色配色方案并降低亮度。如果你在视图中使用了鲜艳的颜色(比如进度条用了亮红色),需要通过 isLuminanceReduced 环境值检测这个状态,主动降低亮度元素的视觉强度。这不是一个可选项——如果你的 Live Activity 在 Always On 下 readability 很差,用户会在 Smart Stack 中快速把它划走。
代码片段
添加 Smart Stack 支持
在 WidgetConfiguration 上添加修饰符,让 Live Activity 支持 Apple Watch。
// 在你的 ActivityWidgetConfiguration 中添加 supplementalActivityFamilies
@main
struct DeliveryLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DeliveryAttributes.self) { context in
// Lock Screen 视图
DeliveryLockScreenView(context: context)
}
dynamicIsland: { context in
DynamicIsland { /* expanded */ }
} compactLeading: {
Text("ETA")
} compactTrailing: {
Text(context.state.estimatedDelivery)
}
// 关键:声明支持 Smart Stack(.small 尺寸)
.supplementalActivityFamilies([.small])
}
}
坑:不添加这个修饰符的话,Smart Stack 会 fallback 到 compact leading/trailing 视图,空间利用率很低。
根据 activityFamily 区分布局
利用环境值在同一个视图中为不同设备提供最优布局。
struct DeliveryContentView: View {
@Environment(\.activityFamily) var activityFamily
let context: ActivityViewContext<DeliveryAttributes>
var body: some View {
switch activityFamily {
case .small:
// Apple Watch Smart Stack 布局:精简信息
HStack {
VStack(alignment: .leading) {
Text("配送中")
.font(.caption2)
Text(context.state.estimatedDelivery)
.font(.title3.bold())
}
Spacer()
Gauge(value: context.state.progress) {
EmptyView()
}
}
case .medium:
// iPhone Lock Screen 布局:完整信息
DeliveryLockScreenView(context: context)
@unknown default:
DeliveryLockScreenView(context: context)
}
}
}
适配 Always On Display 的亮度变化
struct StatusGauge: View {
@Environment(\.isLuminanceReduced) var isLuminanceReduced
let progress: Double
var body: some View {
Gauge(value: progress) {
EmptyView()
}
.gaugeStyle(.accessoryCircularCapacity)
// 亮度降低时使用更柔和的颜色
.tint(isLuminanceReduced ? .green.opacity(0.6) : .green)
}
}
坑:如果你强制设置了 preferredColorScheme 为 .light,系统在 Always On 下会自动切换为 dark appearance 并降低亮度,不需要你手动处理切换逻辑。
最佳实践
已有 Live Activity 的项目: 先确认你的 compact leading/trailing 视图展示的信息是否足够有意义。这是零成本获得 Apple Watch 支持的方式。如果 compact 视图只是展示了一个静态图标或 app 名称,那用户在手表上看到的就是一堆无用信息。第二步再考虑用 supplementalActivityFamilies 提供定制视图。
新项目: 从一开始就把 activityFamily 作为设计约束考虑进去。Lock Screen 视图和 Smart Stack 视图应该一起设计,而不是先做完 iPhone 再硬塞到手表上。如果团队没有 watchOS 开发经验,Live Activity 可能是最低成本的切入点。
有 Watch App 的项目: 可以通过 Info.plist 中的 Supports Launch for Live Activity Attribute Types 配置,让用户点击 Smart Stack 中的 Live Activity 直接打开你的 Watch App,而不是跳回 iPhone。
还有什么值得关注
- 在 Xcode Preview 中可以直接选择 “Content Smart Stack” 预览模式,不需要真机就能看到手表上的效果
- 当 Apple Watch 连接不佳时,系统会优先传递 Start、End 和 alerting 类型的更新,其他更新可能延迟,Smart Stack 会显示”上次连接时间”提示用户信息可能过时
- 即使你没有 Watch App,Live Activity 也能在 Smart Stack 中正常工作,点击后会提供”在 iPhone 上打开”的选项