现在,我们对小组件的创建流程已经有了初步的了解,需要注意,小组件只能用来展示静态的信息,并能支持可交互的组件,例如选择器或滚动视图,当用户点击小组件时,会唤起App本身,并传递一个特殊的URL用来给宿主App做逻辑处理。一个App只能创建一个App Widget,但这并不是说我们只能有一种功能类型的组件,可以通过定义组件包,来提供多个小组件供用户进行使用,示例如下:
struct WidgetExt: Widget {
public var body: some WidgetConfiguration {
StaticConfiguration(kind: "WidgetExt", provider: Provider(), placeholder: PlaceholderView()) { entry in
WidgetExtEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct WidgetExt2: Widget {
public var body: some WidgetConfiguration {
StaticConfiguration(kind: "WidgetExt2", provider: Provider(), placeholder: PlaceholderView()) { entry in
PlaceholderView()
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
@main
struct WidgetsExt: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
WidgetExt()
WidgetExt2()
}
}
需要注意,不同的小组件定义的kind参数要有差异。
3. App Widget 的更新机制
通过前面的Widget初体验,我们知道App Widget可以通过定义时间线来实现视图的动态更新。App Widget使用SwiftUI来进行视图的渲染。Widget有单独的系统进程进行维护,因此即便小组件已经显示在屏幕上,其也并不是一直都是活跃的,开发者可以定义一些时机来对小组件的内容进行更新。
首先,在开发小组件时,我们要清楚所需要的更新时机。例如对于天气类小组件,可能需要每3小时对组件进行一次更新。当我们定义小组件Widget时,需要指定一个TimelineProvider来对其更新进行驱动,TimelineProvider可以理解为定义了一条时间线,配合官方文档中的一张图片来理解时间线的作用会比较容易:
如上图中所示,其定义时间线为之后每小时进行刷新,由于将时间线的Refresh机制设置为了atEnd,3小时后系统会重新请求新的Timeline策略,上图中将第2次请求Timeline策略是设置为了立即刷新一次,之后由于时间线的Refresh机制设置为了never,之后不会再尝试请求时间线进行组件更新。时间轴的Refresh选项实际上是设置了当已经定义的时间轴执行完成后,系统将采用怎样的策略(是重新请求还是从此结束更新)。例如下图:
上图描述了这样一种逻辑,首先请求的时间线定义在未来3个小时,每小时更新一次,并在2小时候重新请求时间线,2小时后新请求的时间线定义2小时后刷新Widget并指定了2小时候重新请求时间线,再2小时之后,重新请求的时间线定义立即刷新组件,并指定之后不再请求新的时间线,组件刷新从此结束。
除了通过设置Timeline的Refresh机制让Widget请求时间线来进行刷新机制的定义外,宿主App也可以对Widget的刷新机制进行定义。宿主App可以使用WidgetCenter来触发指定Widget的刷新机制更新,如下:
WidgetCenter.shared.reloadTimelines(ofKind: "指定的widget的kind")
同样,WidgetCenter目前也只能使用Swift来调用。
顺便提一下,关于WidgetCenter,其本身非常简单,提供的接口非常精简,如下:
// 获取单例对象
static let shared: WidgetCenter
// 获取当前Widgets的用户自定义配置
/*
struct WidgetInfo {
public let configuration: INIntent?
public let family: WidgetFamily
public let kind: String
}
*/
func getCurrentConfigurations((Result<[WidgetInfo], Error>) -> ())
// 重新刷新某个Widget的时间线
func reloadTimelines(ofKind: String)
// 刷新所有Widget的时间线
func reloadAllTimelines()
4. 可配置的Widget组件
前面我们所介绍的构建小组件的方式,虽然可以通过时间线做部分更新逻辑,但对用户来说,依然是静态的。用户不能够根据自己的偏好对组件进行配置,还以天气类组件为例,有些用户可能关心的是空气质量,湿度等信息,有些用户可能只关心阴天雨天的信息,由于小组件的显示空间有限,有时候你无法将所有的信息都展示在组件内,因此让用户选择他感兴趣的信息进行小组件的配置非常重要。
首先,如果要让我们开发的Widget可以支持用户配置,需要在Widget的target工程中添加一个配置属性表文件,使用Xcode新建一个SiriKit Intent Definition File的文件,如下图所示:
之后,需要创建一个新的Intent配置,
之后,我们可以添加一系列的用户配置项,系统提供了各种类型的配置项,如让用户传入字符串信息的配置项,开关配置项,日期配置项等等,如下图:
之后,重新运行Widget,我们的小组件就以支持用户配置功能,用户可以编辑小组件进行设置,如下图所示:
当用户修改了配置项后,组件会重新请求Timeline时间线,在timeline回调方法中,会传入configuration对象,用来存储用户的配置信息,如下:
public func timeline(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
// configuration中存放用户配置信息
var entries: [SimpleEntry] = []
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, configuration: configuration)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
上面演示的这种配置方式,适用于当配置项固定的场景,更多时候,可能连配置项都是动态的,比如我们的应用会根据服务端的状态来提供不同的服务,这时可提供给用户开启的服务项目就是动态的。Widget的配置项也支持动态进行配置,这需要使用到Intents Extension的相关功能,本篇博客就不再过多介绍。
结语:
App Widgets本身并没有什么新意,只是扩大了iOS系统中组件的能力,这从一定程度上可以带给用户更好的服务和更多元的交互体验。脱离App Widgets这个功能的产品意义本身,iOS 14推出这个功能还有一点非常令人惊讶,就是App Widgets只能使用SwiftUI进行开发,这或许从另一个角度暗示了Swift在未来的推广力度,与iOS开发所使用语言的最终方向。