WWDC Quick Look 💓 By SwiftGGTeam
Meet WidgetKit

Meet WidgetKit

观看原视频

Highlight

WidgetKit 在 iOS 14 用 SwiftUI 和 timeline 机制把 App 内容预先交给系统,让主屏幕、Today View、iPad 主屏和 macOS Big Sur 通知中心展示可一眼读取、可点击跳回 App 的信息卡片。

核心内容

Home Screen 上的空间很贵。用户一天会回到主屏幕很多次,每次只停留几秒。一个小尺寸 widget 只有大约四个图标的面积,如果里面塞按钮、滚动列表或复杂控件,用户还没看懂就已经离开了。

这也是 WidgetKit 的起点。Apple 把 widget 定义成可扫一眼的内容投射,而不是持续运行的小 App。内容是重点,点击只是把用户带回主 App;在小尺寸里,整个 widget 只有一个 tap target。到了 medium 和 large,SwiftUI 的 Link 才能让不同区域进入不同位置。(01:50, 12:27

新的系统能力解决了两个老问题。第一,widget 不需要等 App 进程启动后再画界面。WidgetKit extension 在后台返回一组带时间的 SwiftUI 视图,系统把这些 view hierarchy 按 timeline 保存并在正确时间展示。第二,同一份预先准备好的内容还能被 Widget Gallery 使用,用户添加前看到的就是实际 widget 状态。(05:44, 13:23

WidgetKit 还把相关性和个性化放进模型里。Smart Stacks 会用 on-device intelligence 自动把合适的 widget 转到顶部;开发者可以用 Siri Shortcuts donations 帮系统理解用户习惯,也可以在 timeline entry 上提供 relevance。配置则交给 Intents,系统可以从 Intent 自动生成编辑界面,用户长按 widget 就能选择城市、股票或其他参数。(02:51, 04:05, 21:13

所以,做 WidgetKit 的第一步不是写一个缩小版首页。更稳的路径是先找出 App 中最适合被扫一眼读取的信息,再决定它的大小、更新时间、点击落点和是否需要配置。

详细内容

定义一个 widget kind

08:10)一个 widget definition 由 kindconfigurationsupportedFamiliesplaceholder 组成。kind 让一个 extension 支持多个 widget 类型;StaticConfiguration 适合不需要用户配置的场景;placeholder 只能展示这个 widget 类型的代表性内容,不能包含用户数据。

@main
public struct SampleWidget: Widget {
    private let kind: String = "SampleWidget"

    public var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind,
                            provider: Provider(),
                            placeholder: PlaceholderView()) { entry in
                                SampleWidgetEntryView(entry: entry)
                            }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

关键点:

  • @main 标记 widget extension 的入口。
  • kind 是系统识别这一类 widget 的字符串。
  • provider 负责提供 snapshot 和 timeline。
  • placeholder 是默认展示内容,系统只会偶尔请求它。
  • configurationDisplayNamedescription 会出现在添加 widget 的界面里。

用 timeline 驱动显示

13:13)WidgetKit 的运行模型分成三种 UI:placeholder、snapshot 和 timeline。snapshot 用于快速返回一个 entry,Widget Gallery 通常会显示这个真实 widget 预览。timeline 是一组 entry 和日期,告诉系统每个时间点应该展示哪一个视图。

public struct Provider: TimelineProvider {

    public func snapshot(with context: Context,
                         completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date())
        completion(entry)
    }

    public func timeline(with context: Context,
                         completion: @escaping (Timeline<Entry>) -> ()) {
        let entry = SimpleEntry(date: Date())
        let timeline = Timeline(entries: [entry, entry], policy: .atEnd)
        completion(timeline)
    }
}

关键点:

  • snapshot 返回单个 entry,目标是快。
  • timeline 返回 entry 数组和 reload policy。
  • .atEnd 表示系统在 timeline 结束后请求下一条 timeline。
  • transcript 还提到可以指定某个日期后 reload,也可以告诉系统不要 reload。
  • timeline entry 会被系统序列化到磁盘,主屏幕展示时不需要启动 App 的 launcher 流程。

控制刷新,不做实时运行体验

15:03)reload 是系统唤醒 extension 并请求新 timeline 的时机。系统会参考 reload policy、widget 的查看频率和设备环境变化。App 收到 background notification,或用户在前台 App 做了会影响 widget 的更改时,可以通过 WidgetCenter 请求刷新。

关键点:

  • 常见 timeline 应该覆盖数天内容,而不是只返回当前一刻。
  • 频繁查看的 widget 会得到更多 reload,较少查看的 widget 会得到更少 reload。
  • WidgetCenter 可以按 kind reload,也可以 reload 全部 timeline,还能读取当前配置列表。
  • 需要访问服务器时,可以使用 background URLSessions,结果通过 onBackgroundURLSessionEvents 交给 extension。
  • background 处理和网络都有系统预算,widget 不适合每秒更新。

用 Intents 做配置

18:50)如果 widget 需要个性化,WidgetKit 用 Intents 承载配置。Weather widget 的城市、Stocks widget 的股票符号,都是典型参数。iOS 14 还让 Intents 支持 in-app Intent handling,App 可以直接回答配置界面里的问题。

@main
public struct SampleWidget: Widget {
    private let kind: String = "SampleWidget"

    public var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind,
                    intent: ConfigurationIntent.self
                            provider: Provider(),
                            placeholder: PlaceholderView()) { entry in
                                SampleWidgetEntryView(entry: entry)
                            }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

关键点:

  • IntentConfiguration 替换 StaticConfiguration
  • ConfigurationIntent.self 让系统知道这个 widget 使用哪一类 Intent 参数。
  • 用户编辑 widget 时,系统根据 Intent 自动生成配置界面。
  • 如果参数需要动态选项,Intents extension 或 App 可以返回候选值。

让 timeline 接收配置和 relevance

20:54)配置进入 timeline 后,provider 也要换成 IntentTimelineProvider。timeline 方法会收到 configuration,开发者用这些 Intent 参数生成对应 entry。Smart Stack 相关性则通过 Shortcuts donations 和 TimelineEntryRelevance 帮系统判断何时把 widget 转到顶部。

public struct Provider: IntentTimelineProvider {

    public func timeline(for configuration: ConfigurationIntent, with context: Context,
                         completion: @escaping (Timeline<Entry>) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration)

        // generate a timeline based on the values of the Intent

       completion(timeline)
    }
}

关键点:

  • timeline(for:with:completion:) 会拿到用户选择的 configuration
  • entry 可以保存这个 configuration,用它决定展示内容。
  • 如果 widget 背后使用相同的 INIntent,用户在 App 中执行相关动作后捐赠 Shortcuts,有机会帮助 Smart Stack 判断时机。
  • TimelineEntryRelevance 可以提供 scoreduration;分数是相对开发者自己过去提供的 entry 来解释。

核心启发

  • 做什么:把 App 里最常被打开的一眼信息做成 small widget。 为什么值得做:transcript 反复强调小尺寸只有大约四个图标面积,用户停留时间很短。最适合放天气、下一场会议、今日进度这类单一事实。 怎么开始:先做一个 StaticConfiguration,写一个只展示核心数字或状态的 SwiftUI view,再用 widgetURL 把点击带回对应页面。

  • 做什么:为日程、训练、待办这类时间敏感内容生成全天 timeline。 为什么值得做:WidgetKit 的核心能力是提前返回带时间的 entry,系统按时间展示它们。Calendar 的例子说明 extension 可以提前准备会议前、会议中、下一场会议后的视图。 怎么开始:实现 TimelineProvider,把未来几个关键时间点生成 entry,先用 .atEnd 或指定日期的 reload policy 验证刷新节奏。

  • 做什么:给可选择内容的 widget 加 Intent 配置。 为什么值得做:Weather 和 Stocks 的例子都来自 transcript。用户可以在 widget 编辑界面选择城市、股票或列表,系统会用 Intent 自动生成 UI。 怎么开始:把 widget definition 从 StaticConfiguration 改成 IntentConfiguration,再让 provider 实现 IntentTimelineProvider,用 configuration 生成不同 timeline。

  • 做什么:让 Smart Stack 在合适时机展示你的 widget。 为什么值得做:Smart Stacks 会自动把相关 widget 转到顶部。Siri Shortcuts donations 和 TimelineEntryRelevance 都能给系统更多信号。 怎么开始:在用户完成可重复动作时捐赠 Shortcuts;对有明确时效的 timeline entry 设置 relevance score 和 duration。

关联 Session

评论

GitHub Issues · utterances