Widget 新特性
What's new in widgets
2025年6月9日
一句话判断
Widget 终于能从服务端推送更新了——不再需要 App 主动轮询或者依赖 Timeline 调度;visionOS 和 CarPlay 的加入让 Widget 的覆盖面又扩大了一圈;watchOS 的 Relevance Widget 则是直接解决了”不相关 Widget 堵 Smart Stack”的老问题。
这场 Session 讲了什么
三件大事。
第一,Widget 跑到了新平台。visionOS 26 原生支持 Widget,iOS/iPadOS 上的已有 Widget 自动可用,还新增了 systemExtraLargePortrait 竖向尺寸。Widget 在 visionOS 里可以钉在墙面上,支持 elevated 和 recessed 两种样式,还有 paper 纹理选项。远距离查看时会切换到 simplified levelOfDetail,简化布局提升可读性。CarPlay 上 Widget 以 StandBy 样式显示在仪表盘左侧,Live Activity 也可以在 CarPlay 桌面展示了。macOS Tahoe 的菜单栏可以直接放 Control,Live Activity 从 iPhone 自动同步到 macOS 菜单栏。
第二,watchOS 26 引入了 Relevance Widget。不同于传统 Timeline Widget 在 Smart Stack 里被动等待被推荐,Relevance Widget 可以主动声明自己的相关时间段——在这个时间段内才出现在 Smart Stack 里,过了就消失。多个相关配置还能同时展示多个实例。
第三,也是开发者最关心的:Push Widget Updates。服务端可以通过 APNs 直接告诉 WidgetKit “数据变了,该刷新了”。配合 WidgetPushHandler 协议,Widget 注册自己的 push token,服务端发一个 content-available 推送就能触发 Widget 刷新。预算机制和 Timeline 调度一致——系统节流以省电。
值得深挖的点
Accented Rendering Mode 的图像适配
iOS 26 的桌面可以切换到玻璃或色彩主题,此时 Widget 内容会全部转为白色(或主题色)。对图片内容的影响特别大——不透明图片变成全白,半透明渐变保持透明度但颜色变白。Session 给出了五种 widgetAccentedRenderingMode 选项:nil(默认,全白)、accented(主题色着色)、desaturated(去饱和)、accentedDesaturated(两者叠加)、fullColor(原色显示,watchOS 忽略)。
实操建议:大部分图片用 desaturated 或 accentedDesaturated,让图片融入主题而不丢失辨识度。媒体内容(专辑封面、书的封面)用 fullColor 保留原始色彩。用 widgetRenderingMode 环境变量在布局层面做条件渲染——accented 模式下可能需要完全不同的布局。
Push Widget Updates 的适用边界
Push 更新不是万能的。Session 明确说了三种场景的分工:紧急/重要通知走 User Notification;限时高频更新(订单追踪、体育比分)走 Live Activity;Push Widget Updates 适合的是”数据可能在其他设备或服务端变化,需要保持一致”的场景。
预算机制意味着不能高频推送——服务端要做节流。开发测试阶段可以用 Settings 里的 WidgetKit Developer Mode 关闭预算限制。
Relevance Widget 的设计哲学
Relevance Widget 和传统 Timeline Widget 是互补关系,不是替代。Timeline Widget 适合”数据在固定时间点变化”的场景(天气、股票)。Relevance Widget 适合”内容本身有时间段相关性”的场景(Happy Hour、会议、航班)。如果你的 Widget 在大部分时间里没有价值,应该考虑用 Relevance Widget,避免占用 Smart Stack 空间。
代码片段
Push Widget Handler
struct CaffeineWidgetPushHandler: WidgetPushHandler {
func pushTokenDidChange(token: Data) async {
// 把 token 发给服务端
await ServerAPI.registerWidgetToken(token)
}
func widgetsDidChange() async {
// Widget 配置变化时通知服务端
await ServerAPI.updateWidgetInfo()
}
}
// Widget 配置
struct CaffeineWidget: Widget {
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: "caffeine", ...) { entry in
CaffeineWidgetView(entry: entry)
}
.pushHandler(CaffeineWidgetPushHandler.self)
}
}
坑:推送的 APNs topic 必须是 bundleID.push-type.widgets,type 必须是 widgets。在 aps body 里设 content-changed: true 就够了,不需要 badge 或 alert。
Relevance Widget
struct HappyHourProvider: RelevanceEntriesProvider {
func placeholder() -> HappyHourEntry { /* 默认条目 */ }
func relevance() async throws -> [Relevance<HappyHourConfig>] {
let shops = try await fetchHappyHours()
return shops.map { shop in
Relevance(
configuration: shop,
relevance: .init(dateInterval: shop.happyHourInterval)
)
}
}
func entry(for config: HappyHourConfig) async throws -> HappyHourEntry {
HappyHourEntry(shop: config.shop, timeRange: config.timeRange)
}
}
坑:RelevanceEntriesProvider 的 entry 方法只提供一个 entry——不像 TimelineProvider 那样提供一组。如果需要额外数据,可以在这个 async 方法里获取。
visionOS Widget 的 Level of Detail
struct CaffeineWidgetView: View {
@Environment(\.levelOfDetail) var levelOfDetail
var body: some View {
VStack {
Text("今日咖啡因")
Text("\(totalCaffeine) mg")
.font(levelOfDetail == .simplified ? .largeTitle : .title)
if levelOfDetail == .default {
Button(intent: LogDrinkIntent()) {
Label("记录一杯", systemImage: "cup.and.saucer")
}
}
}
}
}
坑:levelOfDetail 变化时有动画过渡,但如果你在 simplified 模式下完全隐藏了某个视图,切换回来时要确保状态没有丢失。
最佳实践
Accented mode 适配应该在 Widget 开发早期就考虑,不要等产品出来再改。特别是包含照片或品牌色彩图片的 Widget——accident mode 下用户体验差异极大。建议每种 Widget 都用模拟器的 accented mode 预览一遍。
Push Widget Updates 的服务端实现要注意节流。如果你的数据每秒都在变(比如计步器),不要每秒都推——用 Timeline 的定时刷新反而更合理。Push 适合”偶尔但需要及时”的场景。
Live Activity 在 CarPlay 上的适配只需要加一行 supplementalActivityFamilies(.small),收益很大——同时改善了 CarPlay 和 Apple Watch 的展示效果。
还有什么值得关注
- visionOS 的 widgetTexture 修饰符可以选 glass(默认)或 paper(海报风格),配合 systemExtraLargePortrait 竖向尺寸效果很好。
- watchOS 的 Control 现在可以放在 Smart Stack 里,显示符号、标题和当前值。
- macOS Tahoe 的 Control 支持 menu bar 直接放置,加上 iPhone Mirroring 同步的 Live Activity,macOS 的信息密度提升了不少。
- Push Widget Updates 跨所有支持 Widget 的平台生效,一次推送更新设备上所有启用了 push 的 Widget。