WWDC Quick Look 💓 By SwiftGGTeam
Widgets Code-along, part 2: Alternate timelines

Widgets Code-along, part 2: Alternate timelines

观看原视频

Highlight

本场 code-along 把第一集的 small widget 扩展到 systemMedium,使用 TimelineReloadPolicy 生成治疗完成前的多条 timeline entry,并通过 SiriKit custom intent、widgetURL 和 SwiftUI Link 支持用户配置与深度链接。

核心内容

第一集已经让 Emoji Rangers 的 Power Panda 出现在主屏幕上,但它还只是一个 systemSmall widget。用户打开 Widget Gallery 时,如果一个 widget 支持多种尺寸,系统会让用户选择不同 family。问题是,small 版只够放一个头像,medium 版多出来的空间应该承载更多角色信息。

这场 code-along 先补齐 family 适配,再进入真正的 timeline。角色会随时间恢复生命值,App 已经知道角色什么时候完全恢复。继续让 widget 请求刷新会浪费系统调度机会;更好的做法是在 provider 里一次生成到恢复完成前的 timeline entry,让 WidgetKit 按时间展示。

后半段把这个 widget 从固定角色推进到可配置体验。用户可以在主屏幕长按 widget,进入 Edit Widget,选择 Power Panda、Egghead 或 Spouty。选好角色后,点击 widget 还应该直接进入对应角色详情页。WidgetKit 负责展示,SiriKit custom intent 负责配置,widgetURL 和 SwiftUI Link 负责跳回 App。

详细内容

1. 同一个 widget 支持 small 和 medium

03:46)第一步是把上一集只支持 systemSmall 的 widget 扩展到 systemMedium。WidgetKit 提供 widgetFamily environment value,视图可以根据当前 family 返回不同布局。

本场没有官方 Code tab 片段。下面只整理 transcript 明确出现的 API 和视图结构,用来说明代码形状。

supportedFamilies([.systemSmall, .systemMedium])

@Environment(\.widgetFamily) var family

switch family {
case .systemSmall:
    AvatarView(character: selectedCharacter)
default:
    HStack {
        AvatarView(character: selectedCharacter)
        Text(selectedCharacter.bio)
    }
}

关键点:

  • supportedFamilies 从第一集的 single family 扩展到 systemSmallsystemMedium
  • widgetFamily 让同一个 entry view 知道自己正在用哪个 family 渲染。
  • systemSmall 继续使用 AvatarView
  • medium 版本把 AvatarView 放进 HStack,旁边显示角色 bio。
  • transcript 还演示了给 small 和 medium 都补背景色与前景色,让预览接近真实主屏幕效果。

2. 用完整 timeline 替代不必要的刷新

01:12)WidgetKit 的 timeline reload policy 有三种:atEndafterneveratEnd 会在最后一条 entry 展示后开始调度更新;after 会在指定日期开始调度;never 表示系统不会独立更新,需要开发者通过 Widget Center API 显式 reload。

07:39)Emoji Rangers 的角色会随时间恢复。既然 App 知道完全恢复的 endDate,provider 可以按一分钟间隔生成 entry,直到角色恢复完成。

let selectedCharacter = configuration.hero
let endDate = selectedCharacter.fullHealthDate
let interval = oneMinute
var currentDate = Date()
var entries: [Entry] = []

while currentDate < endDate {
    entries.append(Entry(date: currentDate, character: selectedCharacter))
    currentDate += interval
}

Timeline(entries: entries, policy: .atEnd)

关键点:

  • selectedCharacter 是当前 widget 要显示的角色。
  • endDate 是角色完全恢复的时间点。
  • transcript 明确说更新时间间隔是一分钟,从 currentDate 开始。
  • 每次循环创建一条带 currentDate 的 entry,再把时间前进一步。
  • timeline 耗尽前,WidgetKit 不需要重新调用 provider 取数据。
  • atEndafter 都只是最早可刷新时间,系统会根据用户体验和调度情况安排实际刷新。

3. 用 relevance 给 Smart Stack 提示

09:00)timeline entry 还有一个可选的 relevance。当 widget 位于 stack 中时,系统可以根据这个提示把 widget 轮换到更合适的位置。

let relevance = selectedCharacter.healthLevel

Entry(
    date: currentDate,
    character: selectedCharacter,
    relevance: relevance
)

关键点:

  • relevance 的取值范围由开发者自己定义。
  • session 选择直接使用角色生命值,因为完全恢复是这个 widget 最重要的状态。
  • relevance 是系统智能的提示,不是强制展示命令。

4. 用 SiriKit custom intent 做主屏幕配置

09:56)timeline 完成后,widget 仍然只显示 Power Panda。演示接着用 SiriKit Intent Definition file 增加配置项,让用户在 Home Screen 上选择角色。

Intent Definition file
category: information view
parameter: hero
enum values: Panda, Egghead, Spouty

StaticConfiguration -> IntentConfiguration
TimelineProvider -> IntentTimelineProvider
intent: CharacterSelectionIntent
snapshot(configuration)
timeline(configuration)

关键点:

  • Intent Definition file 需要同时加入 widget target 和 app target。
  • intent category 在演示中设置为 information view。
  • hero 参数是 enum,演示里补齐 Panda、Egghead 和 Spouty。
  • widget 类型从 StaticConfiguration 改为 IntentConfiguration
  • provider 从 timeline provider 改为 intent timeline provider。
  • snapshot 和 timeline 方法会多收到一个 configuration 参数,provider 再把配置映射到 App 里的角色定义。

5. 点击 widget 回到对应内容

02:54)Widget 没有动画或自定义交互,但可以 deep link 到 App。systemSmall 是一个完整点击区域;systemMediumsystemLarge 可以用 SwiftUI Link 创建多个可点击区域。

13:42)演示在配置完成后给视图加 widgetURL modifier。这样用户点击当前角色的 widget,会直接进入对应角色详情页。

AvatarView(character: selectedCharacter)
    .widgetURL(selectedCharacter.detailURL)

Link(destination: selectedCharacter.detailURL) {
    AvatarView(character: selectedCharacter)
}

关键点:

  • widgetURL 用在 small 和 medium 视图上,让点击进入当前角色。
  • 用户切换 favorite Ranger 后,跳转目标也跟着配置变化。
  • medium 和 large family 有空间放多个点击区域时,可以使用 SwiftUI Link

核心启发

1. 做一个恢复倒计时 widget

做什么:把游戏角色、健身恢复、学习冷却时间这类已知结束时间的状态放到主屏幕。

为什么值得做:session 的核心例子就是角色会随时间恢复,并且 App 知道完全恢复的 endDate

怎么开始:在 timeline provider 中从当前时间开始,每分钟生成一条 entry,直到 endDate,policy 先用 .atEnd

2. 为同一内容设计 small 和 medium 两套读法

做什么:small 只显示一个关键状态,medium 增加一段补充信息。

为什么值得做:transcript 演示了 small 继续用 AvatarView,medium 用 HStack 加上角色 bio。

怎么开始:读取 widgetFamily environment value,用 switch 给不同 family 返回不同布局,再同时更新 primary view 和 placeholder。

3. 给用户一个主屏幕配置入口

做什么:让用户长按 widget 后选择要展示的角色、项目或账号。

为什么值得做:session 通过 CharacterSelectionIntent 让用户在 Edit Widget 里选择 Power Panda、Egghead 或 Spouty。

怎么开始:创建 Intent Definition file,把 category 设为 information view,添加 enum 参数,然后把 widget 改成 IntentConfiguration

4. 给 Smart Stack 提供排序信号

做什么:在状态最重要的时间点提高 widget 的 relevance。

为什么值得做:session 明确说 widget 位于 stack 中时,系统可以根据 relevance 智能轮换。

怎么开始:选一个能代表优先级的数值。Emoji Rangers 用 health level,因为完全恢复是最重要的状态。

5. 让点击动作进入正确详情页

做什么:用户点 widget 后直接进入当前配置对应的详情页。

为什么值得做:session 演示配置成 Spouty 后,点击 widget 会进入 Spouty 的信息页。

怎么开始:给 small/medium 视图添加 widgetURL。如果 medium 或 large 中有多个目标区域,用 SwiftUI Link 拆出局部点击区域。

关联 Session

评论

GitHub Issues · utterances