2020 年 6 月 22 日,苹果召开了第一次线上的开发者大会 - WWDC20。这可谓是一次可以载入史册的发布会,宣布了 ARM 架构 Mac 芯片、软硬件的生态大统一、iOS 14 系统界面大改等一系列激动人心的消息。
当然,最让我感兴趣的就是让 iOS 界面大改的 Widget 了。过去几年,iOS 的桌面交互体验可谓是一言难尽,Widget 的加入无疑是一次比较大的破局。在看发布会的时候,我的脑海里就浮现出一个问题:“这会是下一个互联网公司竞争的流量入口吗?”
先不抛结论,让我们先看一下 WWDC20 介绍了哪些新东西。
什么是 Widget?
Widget 不是一个小型的 App,它是一种新的桌面内容展现形式,主要是用于弥补主应用程序无法及时展示用户所关心的数据。如下图所示:
一个优秀的 Widget 需要有三个特点:简单明了(Glanceable)、恰当展示(Relevant)、个性化定制(Personalized)
▐ 简单明了(Glanceable)
Widget 不是一个小型的 App,这句话被反复提起。一般用户每天进入主屏幕的次数超过 90 次,但停留的总时长不过几分钟。通常来说用户只会在主屏幕上停留片刻时间,就会跳转到其他地方,所以并不需要任何复杂的交互设计来增强 Widget 的作用,也不需要复杂的样式来丰富 Widget 的内容,简单明了的内容才是 Widget 的关键。
和安卓的 Widget 不太一样,苹果设计的 Widget 并不支持任何复杂交互行为,也不建议大家设计过于复杂的样式来呈现内容,这也非常符合苹果对于主屏幕的改进一直保持克制的特点。
▐ 恰当展示(Relevant)
苹果期望 Widget 可以和正在执行或者考虑的事情紧密的结合。比如,早上起床,用户最关心天气怎么样,Widget 可以展示一下天气情况;起床后,用户就要了解一下一天的行程,Widget 可以展示一下 Reminders 中的内容;等到一天忙完了,准备睡觉的时候,可以用 Widget 打开音乐稍微放松一下。为此,苹果系统提供了一个叫智能叠放(Smart Stacks)的功能,智能叠放是一个 Widgets 的集合。系统会根据每个人的习惯,借助端智能的能力,自动的显示准确的 Widget 在最顶部。
当然,苹果也考虑到了一些特殊的场景,比如 Widget Gallery 浏览时,提供了 Snapshot 的能力给到开发者可以定制展示样式,当加载内容的时候提供了 Placeholder UI API 而不是单调的 loading 加载框来避免过多的白屏的尴尬局面。这些设计的目的只有一个,苹果期望 Widget 可以在任何特定的场景都可以展示合理的样式。
▐ 个性化定制(Personalized)
Widget 需要一定的定制能力,比如当我添加一个天气 Widget,我只需要关心杭州的天气怎么样。为了实现这个能力,苹果给 Widget 提供了 Configuration 的能力。顾名思义,就是可配置。一共有两种配置类型:
- StaticConfiguration,也就是用户无需配置,展示的内容只和用户信息有关系。
- IntentConfiguration,支持用户配置及用户意图的推测功能。
IntentConfiguration 的实现是基于 Intents.framework,开发过 SiriKit 和 Shortcuts 一定知道 Intents API 是用于了解用户意图的。其实就是一个智能的表单系统,开发者创建一个 SiriKit Intent Definition File 之后,只需要简单的配置,Xcode 会自动帮你生成对应的代码和类型。
当开发者编写完配置之后,会借助 Intents.framework 的能力,在运行的时候直接绘制出一个配置页面(如下图所示),开发者并不需要关心如果编写这个页面。
Widget 的刷新方式
Widget 的刷新方式是很特别的,相当的克制。在展开讲刷新方式之前,要讲一个概念,叫 Timeline。顾名思义,就是时间线,下面的图就是一条 Timeline。
当系统的 WidgetKit 调用 Reload Timeline API 之后,会要求 Widget Extension 的 Timeline Provider 提供一组 TimelineEntry 和 ReloadPolicy,用来后续刷新页面。
这里的概念比较多,我们一个一个来解释。
首先,Widget 的刷新完全由 WidgetCenter控制。开发者无法通过任何 API 去主动刷新 Widget 的页面,只能告知 WidgetCenter,Timeline 需要刷新了。
系统提供了两种方式来驱动 Timeline 的 Reload。System Reloads 和 App-Driven Reloads。
System Reloads: 这个行为由系统主动发起,会调用一次 Reload Timeline 向 Widget 请求下一阶段刷新的数据。系统除了会按时发起 System Reloads 之外,还会借助端智能的能力,动态决策每个不同的 TimeLine 的 System Reloads 的频次。例如被查看次数很大程度上直接决定了 System Reloads 的频率。当然还有一些由于设备环境变化触发的行为也会触发 System Reloads,比如设备时间进行了变更。
App-Driven Reloads:指的是 App 请求 Widget 下一阶段刷新的数据。这里也要分两种场景,应用在前台运行和应用在后台运行。当应用在前台运行的时候,App 可以直接请求WidgetCenter的 API 来触发 Reload Timeline;而当应用处于后台时,后台推送(Background Notification)也可以触发 Reload Timeline。
注意,前面所提到的 Reload Timeline 并不是直接刷新 Widget,而是 WidgetCenter 重新向 Widget 请求下一阶段的数据。而 Timeline Provider 就是提供这个数据的对象。
而 Timeline Provider 提供的数据有两部分,一部分是 TimelineEntry,另外一部分是 ReloadPolicy。
TimelineEntry 是某个时间节点下 Widget 需要呈现的视图信息和时间点。
而 ReloadPolicy 则是接下来这段时间 Timeline 的刷新策略,一共有三种:
- atEnd: 是指 Timeline 执行到最后一个时间片的时候再刷新。
- atAfter: 是指在某个时间以后有规律的刷新。
- never:是指以后不需要刷新了。什么时候需要重新刷新需要 App 重新告知 Widget。
当 Timeline Provider 提供完下一阶段的数据之后,就会停止运行。系统也会根据 entry 的信息,到点对 Widget 的展示内容进行刷新。值得一提的是,WidgetKit 会把 Timelines 所定义的 Entries 对应的 Views 结构信息缓存到磁盘,然后在刷新的时候才通过 JIT 的方式来渲染。这使得系统可以在极低电量开销下为众多 Widgets 处理 Timelines 信息。
简而言之,苹果对 Widget 的刷新相当的克制。开发者无法直接决定 Widget 刷新,只能提供刷新策略。具体的时间和节奏全部由系统来控制。苹果这么做,大概率是为了提高主屏幕的性能和减少电量开销上的考虑。
Widget 和 SwiftUI
Widget 只能用 SwiftUI 来进行开发,确切的说,Widget 的本质是一个随着时间线而更新的 SwiftUI 视图。
当我最开始知道这个限制的时候,说实话是相当震惊的。众所周知,SwiftUI 是一个去年才发布的新技术,而且最开始的时候 SwiftUI 是相当不稳定的,以至于苹果自己都是建议开发者暂时不要用到生产环境上,Widget 作为系统主屏幕的功能,强制使用这么新的技术,会不会太激进了?
显然是不会。苹果要求 Widget 只能使用 SwiftUI 主要是基于几点考虑:
1、SwiftUI 经过一年的发展,有了很大的提升,不仅可以使用 SwiftUI 来构建整个应用程序,而且在一些方面已经优于基于 UIKit 的开发方式了。具体的内容,大家可以看一下《详解 WWDC 20 SwiftUI 的重大改变及核心优势》
2、苹果正在布局跨平台,大统一的策略。Widget 作为系统的核心功能,使用 SwiftUI 是唯一的选择。SwiftUI 精美的 DSL 设计,使得开发者使用一套代码在 iOS、iPadOS、macOS、watchOS 和 tvOS 等多个平台展示不同的样式可以轻松的实现。(Widget 只会在 iOS、iPadOS 以及 macOS 上展示)
3、使用了 SwiftUI 使得 Dynamic Type 和 Dark Mode 等问题适配起来成本很低。
4、只有使用 SwiftUI 才能达到很多对于 Widget 的限制。倘若可以使用 UIKit 开发者可能有无数种办法绕过苹果的限制。比如开发无法使用 UIViewRepresentable 来桥接 UIKit,只要使用任何 UIKit 的元素会直接 Crash。
5、将 Swift 语言和 SwiftUI 的重要程度提升了一大截。
Widget 的展示形式
▐ 一个 App 可以对应多个 Widget Extension
你可以使用WidgetBundle 来进行组装。苹果并没有对 Widget Extension 有数量上的限制。所以为了避免大家开发过多的 Widget Extension 导致搜索起来麻烦,在 Widget Gallery 中只能看到一个条目。
▐ 一个 Widget Extension 一共只有三种尺寸
考虑到简单明了的特点以及手机屏幕的空间有限的问题。苹果只提供了三种样式可以选择,systemSmall(2 * 2 icon 区域)、systemMedium(2*4 icon 区域)、systemLarge(4 * 4 icon 区域)。
▐ 同一种 Widget 可以被多次添加到主屏幕中
而且对于每一个 Widget 来说,都有其对应的独立 TimeLine,相互独立,互不干扰。
▐ 开发者无法开发智能叠放(Smart Stacks)
开发者无法开发一个 Widget 的集合。智能叠放(Smart Stacks)是一个系统特有的能力,对于开发者来说,唯一可以做的就是主动提供相关性信息。前文提到了 Timeline 的数据又一组 TimelineEntry 组成,而每个 TimelineEntry 除了包含时间点和视图信息以外,还可以包含一个 TimelineEntryRelevance 对象,用来表示这个 entry 的相关性。
▐ 不可交互,只可点击
Widget 的 UI 是无状态的,不支持滚动,也不支持像 Switch 一样的互动元素。唯一开放的能力只有通过点击和DeepLink 来唤起主 App。
苹果提供了两种 API 给到开发者,第一种是SwiftUI widgetURL API,代码如下所示:
而 widgetURL 的可点击区域如下:
对于 systemSmall 类型来说,只支持 widgetURL 的方式,但是 systemMedium 和 systemLarge 还可以使用 SwiftUI Link API,代码如下所示:
而 Link 的可点击区域如下:
同时,为了性能和耗电量的考虑。Widget 不能展示视频和动态图像。所以期待通过动效吸引用户眼球的方式可以暂时息熄火了~
总结与展望
Widget 的出现,让 iOS 系统的桌面有了破局,一定会有很多产品都期待借助 Widget 来丰富自己产品的内容表达。
但是,Widget 设计的初衷是简单明了的在恰当的时机展示一些带有个性化定制的内容,为了不让主屏幕的整体使用体验变得复杂,Widget 从技术上就做的很克制,限制了很多很多的能力。因此我认为Widget 不会成为下一个互联网公司竞争的流量入口,它会成为 App 提高用户体验的利器。
从技术角度看,SwiftUI Only 这种看似“激进”的策略其实也是一种信号,其实也是在告诉大家苹果对于 Swift 以及 SwiftUI 的重视程度。
虽然,从目前来看 Pure SwiftUI 的设计,可以做的事情真的很少,但是我也相信,苹果会不断优化 Pure SwiftUI 的能力。让开发者可以以最低的开发成本,适配更多的平台。
最后,也期待大家可以好好研究一下 Widget,结合自己的产品,给到用户极致的用户体验。
参考
iOS 14 Preview
https://www.apple.com.cn/ios/ios-14-preview/
Widgets code-along
https://developer.apple.com/news/?id=yv6so7ie
Meet WidgetKit
https://developer.apple.com/videos/play/wwdc2020/10028/
What's new in SwiftUI
https://developer.apple.com/videos/play/wwdc2020/10041/
Add configuration and intelligence to your widgets
https://developer.apple.com/videos/play/wwdc2020/10194/
Build SwiftUI views for widgets
https://developer.apple.com/videos/play/wwdc2020/10033/
Creating a Widget Extension
https://developer.apple.com/documentation/widgetkit/creating-a-widget-extension
Building Widgets Using WidgetKit and SwiftUI
https://developer.apple.com/documentation/widgetkit/building_widgets_using_widgetkit_and_swiftui
Making a Configurable Widget
https://developer.apple.com/documentation/widgetkit/making-a-configurable-widget
Keeping a Widget Up To Date
https://developer.apple.com/documentation/widgetkit/keeping-a-widget-up-to-date