WidgetKit 入门指北

原创
2020/08/21 08:30
阅读数 6.9K

作者:zvving,iOS 开发者,现就职于字节跳动音乐团队


本主题基于 https://developer.apple.com/videos/play/wwdc2020/10028/ 梳理

概述

WidgetKit 是 WWDC20 备受关注的新特性,支持在 iOS、iPadOS 主屏幕,今日视图以及 macOS 通知栏展示动态信息和个性化内容。

众所周知,Apple 对 iOS 主屏幕的改进一直保持克制,这次引入 Widget 是一次巨大的改动。Widget 如何提供一致的跨平台体验?如何提供实时的个性化内容?如何引入 Widget 同时最大可能减少主屏幕的性能、电量开销?本文作为 WidgetKit 速览,带你快速了解这些内容。

优秀 Widget 的三要素

设计优秀的 Widget,包含如下三个要素

  • Glanceable 一目了然
  • Relevant 高相关性
  • Personalized 个性化

Glanceable 一目了然

Widgets are not mini-apps

Widget 并不是(常驻桌面的)微型应用程序,这句话被反复提起。在一闪而过的快速切换应用的主屏幕里,设计交互复杂的应用界面并不能切合用户的需要。一目了然的内容才是用户关心的唯一要素

通过强大的 SwiftUI,你可以快速实现跨平台体验一致的 Widget,同时非常方便的支持 Dynamic Type、DarkMode 等系统特性,提供一目了然、快速响应的桌面体验。

Relevant 高相关性

一般用户每天进入主屏幕的次数超过 90 次,但停留的总时长不过几分钟。用户可不想在主屏幕见到枯燥的加载界面

WidgetKit 会在正确的时机显示即时的内容,避免每个 Widget 启动、加载、渲染的漫长过程。为了达成这一目标,WidgetKit 统一管理所有 Widget,支持预渲染、复用,并提供合适的更新策略,灵活可控的更新时机。

智能叠放

智能叠放是 Widget 与用户高相关性的又一体现。它是支持多个 Widget 叠放的智能组件。用户可以上下滑动手动切换,更巧妙的是:基于用户的使用场景以及开发者的配置,系统也会智能地为用户展示最需要的 Widget。

Personalized 个性化

在天气 widget 中,基于不同地理位置,语言、摄氏度单位,提供个性化的使用体验。同时支持三种尺寸,更小的尺寸展示核心内容,更大的尺寸则展示更多的相关信息,满足不同用户的需要。

快速开发一个 Widget

配置信息

Configuration

  • Static configuration :静态配置,无用户配置项
  • Intent configuration  :支持用户配置及用户意图推测功能(Intents Extension)

SupportedFamilies

Widget 支持 small、medium、large三种尺寸 ,建议开发者同时提供三种尺寸。

  • Placeholder UI

Placeholder UI  是 Widget 加载中所显示的占位信息,它应该清晰的表达 Widget 所属类型。绝大多数场景下,用户不会看到  Placeholder UI ,只有在修改设备环境配置等个别场景下会碰到。

Widget 交互

Widgets are not mini-apps

Widget UI 是无状态的,不支持滚动,不能展示视频和动态图像。

Widget 为用户提供一目了然的内容展示,在用户需要进一步查看或编辑的时候,通过轻点启动 app 完成顺畅的体验。

用户点击 Widget 中的特定专辑后,通过 Deep links,App 启动后快速展示对应专辑,方便用户的进一步操作。

注意:systemSmall 只支持一个 link ,systemMedium,systemLarge 支持多个

WidgetKit 执行原理

开发者通过 SwiftUI 构建 Views,定义 Timelines 为 Views 提供对应时间所需数据。数据变化时,通过 reload 更新数据。

WidgetKit 统一管理多个 Widgets:序列化 Widget Views 和 Timelines 数据,处理预渲染和复用,响应并处理 System reloads、App-driven reloads,为桌面、Widget Gallery 预览等场景提供高效、即时的 widget 体验。

值得一提的是:WidgetKit 会把 Timelines 所定义的 Entries 对应的 Views 结构信息缓存到磁盘,仅在需要的时候实时渲染特定的 View。这使得系统可以在极低电量开销下为众多 Widgets 处理 Timelines 信息。

Views

  • Placeholder UI:(上文已经介绍)Widget 占位 UI
  • Snapshot

以最快速度返回的视图。并非截图,而是真实体验。

大多数I情况下,timeline 第一条数据可以与 snapshot 相同,这样用户在 Widget Gallery 中看到内容和添加到设备时得到的东西相同。

Timelines

Timeline 是一系列驱动 Views 的数据集合,WidgetKit 随着时间流逝加载对应的 Widget 数据,配合 Reload 策略灵活支持复杂业务场景的变化。

Reloads

Reloads 分两类:System reloads,App-driven reloads。系统会综合判断,确定重新加载 widget 的最佳时间。

System reloads
  • ReloadPolicy:配置 timeline 时定制 reload 策略
    • atEnd
    • after(date: Date)
    • never
  • Widget 被查看越多,将更多的被 reload
  • 设备环境变化也会触发 reload
App-driven reloads

当应用在后台时,后台推送可以触发 reload;当应用在前台时,应用可以主动触发 reload。进而通过 reload 变更全部或特定 timeline entry。

请谨慎的对待 widget reload,仅当相关数据变化需要反映在 widget 中时才触发 relaod,避免无谓的重新加载。

对于后台加载需要网络数据可以通过 Background sessions 完成,相关的系统资源占用将计入 widget 开销,由 WidgetKit 管理对应 background reload 预算。

个性化并理解用户意图

Intents framework

Intents framework 一直被应用在 SiriKit 和 Shortcuts,从现在开始 WidgetKit 也将借助 Intents framework 更好的理解用户意图。

驱动理解用户意图的智能堆栈

在智能堆栈小组件中,开发者通过每一个 TimelineEntryRelevance 可以告知 WidgetKit 用户在特定场景下相关性评分和持续时间。WidgetKit 综合评估不同 Widget 评分,分析用户意图,旋转堆栈到用户关心的 widget。

注意:widget 提供的所有 TimelineEntryRelevance 都会作为理解用户意图相关性的参考

Demo

配置 Widget 是很容易的,如下 SampleWidget 代码已经包含大部分 widget 初始配置:

  • 静态 WidgetConfiguration 需要提供 Provider(),PlaceholderView(),entry -> SampleWidgetEntryView 闭包
  • Provider 中定义 snapshot,构建包含 Entry 集合的 Timeline
  • 最后使用 SwiftUI 实现 PlaceholderView 和 SampleWidgetEntryView 即可
@main
public struct SampleWidgetWidget {
    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.")
    }
}

public struct ProviderTimelineProvider {

    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)
    }
}

总结

终于讲完了,为了理解上的完整性,本文做了很多扩展性的说明,感谢各位读者的耐心阅读;这是 Widget 系列的最后一篇,WidgetKit 的概念很棒,也非常好用!希望大家能真正读懂原理,创造更多优秀的 Widget!

推荐阅读

✨ Apple Widget:下一个顶级流量入口?

✨ 为 Widgets 构建 SwiftUI 视图

《Widgets 边看边写》第一部分:冒险开始了

《Widgets 边看边写》第二部分:Timelines 的基本使用

《Widgets 边看边写》第三部分:Timelines的进阶使用

关注我们

我们是「老司机技术周报」,每周会发布一份关于 iOS 的周报,也会定期分享一些和 iOS 相关的技术。欢迎关注。

支持作者

这篇文章的内容来自于 《WWDC20 内参》。在这里给大家推荐一下这个专栏,专栏目前已经创作了 101 篇文章,只需要 29.9 元。点击【阅读原文】,就可以购买继续阅读 ~

WWDC 内参 系列是由老司机周报、知识小集合以及 SwiftGG 几个技术组织发起的。已经做了几年了,口碑一直不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实际开发经验、苹果文档和视频内容做二次创作。


本文分享自微信公众号 - 老司机技术周报(LSJCoding)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
打赏
0
4 收藏
分享
加载中
更多评论
打赏
0 评论
4 收藏
0
分享
返回顶部
顶部