为 Apple Watch 智能叠放构建 Widget
Build widgets for the Smart Stack on Apple Watch
2023年6月5日
一句话判断
watchOS 10 的 Smart Stack 需要你的 Widget 告诉系统”什么时候我最相关”——通过 TimelineEntryRelevance 的 score 和 duration,以及 RelevantIntentManager 的日期标记,让系统在鸟类来访时自动展示你的 Widget。
这场 Session 讲了什么
watchOS 团队的 Calvin Gaisford 以 Backyard Birds 应用为例,完整演示了如何为 Apple Watch 的 Smart Stack 构建 Widget。
Smart Stack 是 watchOS 10 中展示 Widget 的新方式。系统根据相关性自动选择显示哪个 Widget。开发者需要做两件事来帮助系统做出正确的选择:在 TimelineEntry 中标记相关性(score 和 duration),通过 RelevantIntentManager 注册相关日期。
Widget 使用 AppIntentConfiguration(而非旧的 IntentConfiguration)。配置 Intent 遵循 WidgetConfigurationIntent 协议,直接在 Widget Extension 代码中定义,不需要 Intent Definition File。
Session 完整覆盖了 Widget 的构建流程:定义 ConfigurationIntent、创建 TimelineEntry(含 relevance)、实现 TimelineProvider(placeholder/snapshot/timeline/recommendations)、构建视图、设置相关性管理。
Xcode 新增了 Widget 时间线预览功能,可以在 Canvas 中直接查看 Widget 的不同时间状态。
值得深挖的点
TimelineEntryRelevance 的双参数设计:score 用于同一 Widget 的时间线内部排序(如鸟类来访时 score=10,没有鸟类时 score=0),duration 告诉系统这个相关性持续多久。系统综合所有 Widget 的 relevance 来决定 Smart Stack 中展示什么。
RelevantIntentManager 的外部触发:除了时间线内的 relevance,你还可以通过 RelevantIntentManager 向系统注册特定日期的 intent。比如”后天下午 3 点有个观鸟活动”,系统会在那个时间优先展示你的 Widget。
recommendations 函数的预配置 Widget:这个函数返回一组预配置的 Widget 建议,出现在 Widget 画廊中。对于 Backyard Birds,每个后院就是一个预配置 Widget。
Xcode Widget 时间线预览:Canvas 底部展示时间线条目序列,可以直观看到 Widget 在不同时间点的显示效果。这比运行应用来测试 Widget 高效得多。
代码片段
// Widget 配置 - 使用 AppIntentConfiguration
@main
struct BackyardVisitorsWidget: Widget {
var body: some WidgetConfiguration {
AppIntentConfiguration(
kind: "BackyardVisitors",
intent: BackyardWidgetIntent.self,
provider: BackyardProvider()
) { entry in
BackyardBirdsWidgetEntryView(entry: entry)
}
}
}
// TimelineEntry 添加 relevance
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: BackyardWidgetIntent
let backyard: Backyard
// 标记此条目的相关性
var relevance: TimelineEntryRelevance? {
if let visitor = backyard.visitorEvent(at: date) {
// 有鸟类来访:高相关性
return TimelineEntryRelevance(
score: 10.0,
duration: visitor.endDate.timeIntervalSince(date)
)
}
// 没有鸟类:低相关性
return TimelineEntryRelevance(score: 0.0)
}
}
// TimelineProvider - recommendations 提供预配置 Widget
func recommendations() -> [AppIntentConfiguration<BackyardWidgetIntent, SimpleEntry>.Recommendation] {
// 为每个后院生成一个推荐的 Widget 配置
let backyards = Backyard.allBackyards
return backyards.map { backyard in
AppIntentConfiguration.Recommendation(
intent: BackyardWidgetIntent(backyardID: backyard.id),
description:LocalizedStringResource(stringLiteral: backyard.name)
)
}
}
// RelevantIntentManager - 注册特定日期的相关性
let manager = RelevantIntentManager()
let intent = BackyardWidgetIntent(backyardID: "yard-123")
let relevantDate = Calendar.current.date(byAdding: .day, value: 2, to: Date())!
let relevantIntent = RelevantIntent(
intent: intent,
validDays: [relevantDate],
validTimes: [RelevantTime(beginHour: 14, beginMinute: 0,
endHour: 17, endMinute: 0)]
)
manager.relevantIntents = [relevantIntent]
最佳实践
- relevance score 要有意义地分层:鸟类来访时给高分,没有时给零分。系统会比较所有 Widget 的 score 来决定展示优先级。
- 为每个逻辑单元提供预配置 Widget:通过 recommendations() 让用户快速选择感兴趣的 Widget 变体。
- 利用 RelevantIntentManager 注册未来事件:如果应用知道某些时间点特别重要(如预约、活动),提前注册让系统在合适时机展示。
- 用 Xcode Canvas 预览时间线:不要每次都运行应用来测试 Widget,Canvas 的时间线预览足够高效。
- placeholder 和 snapshot 要快速返回:这两个函数需要即时响应,使用本地数据或随机数据即可。
还有什么值得关注
- Smart Stack 是 watchOS 10 的核心交互方式之一,Widget 的可见性完全取决于相关性评分。
- AppIntentConfiguration 是新标准,SiriKit Intent Configuration 已经过时。新项目应直接使用 App Intents。
- Widget 的时间线粒度需要平衡——太多条目消耗资源,太少条目无法精确表达相关性变化。