Listify Widget 开发笔记
Apple 在 iOS 8 的年代就上线过小插件,命名为 Today Extension,只能存在于 iOS 的通知中心里,随着 WidgetKit 出现,之前的 Today Extension 将会慢慢被淘汰了。 与之前的 Today Extension 相比,Widget 有这些不同。
设计
Today Extension 的背景颜色根据官方推荐都是透明的,方便与通知中心融入,尽管背景颜色可以调节成其他的颜色,还是很少看见有开发者将Today Extension 背景设置为其他的颜色
Widget 的设计显得丰富一些,Apple 没有刻意规定Widget的背景颜色是什么样子,可以随意更换。Listify 使用了 暗色与白色两个背景颜色分别对应 iOS 的白天模式与夜间模式。
尺寸
Today Extension 拥有两种尺寸, compact 和 expanded,默认展示compact,用户点击右上角的箭头可以使 Today Extension 扩大为 expanded 模式,expanded模式下高度不固定,看开发者是如何规定的,但是有一个最大高度,超过最大高度后的视图会被截短。
WidgetKit 只有大,中,小三个尺寸,每种尺寸的大小不可改变,但是开发者可以选择只开发其中一种尺寸。
数据交换
Today Extension 与 WidgetKit 一样,均没有办法直接读取主 App 的内容,比如 Listify 的 Widget 无法直接读取 App 的数据库取得数据,只能使用UserDefault 或者 CloudKit 读取数据,Listify 的 Today Extension 与 Widget 均使用 UserDefault 交换数据。
交互
WidgetKit 无法实现交互,点击 Widget 后只可以跳转到 App,开发者仅可知道用户点击了Widget 的某个区域并且根据这个信息显示不同的视图仅此而已。目前我发现能够实现Widget交互的App只有官方的Shortcut,应该是官方开了后门。
Today Extension 只可以接受点击交互,但是会自动屏蔽掉滚动操作,如果你实现了一个 TableView,这个TableView 可以响应点击事件,但是无法响应滚动事件。并且 Today Extension 官方上是无法实现弹出键盘进行输入的。
其他
Widget 可以同时存在多个实例,开发者可以使用 Intent 对Widget显示内容进行配置,比如用户可以在添加 Listify Widget 时候选择 Widget 显示的是哪个清单的内容。
Today Extension 只能存在一个,在通知中心里不可能同时存在两个一样的控件。
开发
视图层面,WidgetKit 只能使用 SwiftUI 开发,而Today Extension 使用 UIKit 开发。
WidgetKit 只能支持基本视图,无法支持 TableView 这样的视图。
开发这里我更推荐去看WWDC官方的文档和开发视频,这里只对相关的解决方案进行大致解释,不会出现过多可以直接使用的代码。
我在开发 Listify WidgetKit 的时候学习了一下相关的资源:
WidgetKit 的开发文档: Apple Developer Documentation
下面这个视频讲解了 SwiftUI 和 WidgetKit 的基础。
Build SwiftUI views for widgets - WWDC 2020 - Videos - Apple Developer
下面三个视频是实战型的
Widgets Code-along, part 1: The adventure begins - WWDC 2020 - Videos - Apple Developer
Widgets Code-along, part 2: Alternate timelines - WWDC 2020 - Videos - Apple Developer
Widgets Code-along, part 3: Advancing timelines - WWDC 2020 - Videos - Apple Developer
WidgetKit 开发依赖于一个 Timeline 的概念,顾名思义就是时间轴的意思,iOS 会定期向你的 Widget 询问一个时间轴,这个时间轴就是你的 Widget 未来一段时间展示的样子,比如天气 App 的 Widget,在操作系统向 Widget 询问时间轴的时候,天气 App 应该要返回未来5个小时,每个小时的天气数据。TimelineEntry就是时间轴中一项的抽象。
以下就是 Listify Widget使用的 TimelineEntry,每个TimelineEntry必须包含一个date 日期,用于确定这个 Entry 的展示时间,ListEntity 是 Listify 中用于抽象一个清单的模型。
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: SelectListIntent
let listEntity: ListEntity
}
在新建一个 WidgetKit 项目之后, Xcode 首先会生成一个 IntentTimelineProvider,IntentTimelineProvider 用于给小插件渲染提供数据,这个struct实现了三个函数,这三个函数也同样非常直观。
当然iOS并不会确保在你编写的时间准确刷新,你生成的Entry中的date属性只是用来告诉 iOS 你的小插件需要刷新,至于 iOS 时候会在那个时刻调用你的 getSnapshot,取决于操作系统了。本来Listify 的小号 Widget 上会展示当天的日期,后来由于我无法保证新的一天00:00的时候让 iOS 刷新我的小插件,索性不去展示当天的日期。
建立工程后也会默认实现一个 struct WidgetEntryView,继承自 SwiftUI 中的View,其中包含一个 entry (就是上面提到的 TimelineEntry)以及一个 叫做body的View,开发者需要复写body来返回相应的视图,下面是 Listify 的 Widget 渲染代码,当entry可用时候,根据 Widget 的大小生成了三个不同的控件 SmallWidgetView,MediumWidgetView,LargeWidgetView,这三个视图是我自己实现的,分别对应大中小三个尺寸。
struct Listify_WidgetEntryView : View {
var entry: Provider.Entry?
@Environment(\.widgetFamily) var family
@ViewBuilder
var body: some View {
ZStack{
Color("WidgetBackground")
if let e = entry {
switch family {
case .systemSmall:
SmallWidgetView(entry: e)
case .systemMedium:
MediumWidgetView(entry: e)
case .systemLarge:
LargeWidgetView(entry: e)
@unknown default:
FailureStateView()
}
}else{
FailureStateView()
}
}
}
}
Listify 小插件有一个功能是列表选择,当用户添加 Listify Widget 的时候,Listify 会询问用户添加的 Widget 要显示哪一个清单,这个时候就需要用到 Intent 了,Intent 相关的知识 SiriKit 中也会用到。
这里我同样推荐 WWDC 的官方视频 Add configuration and intelligence to your widgets - WWDC 2020 - Videos - Apple Developer 我就是根据这个视频开发的。