Jun 28

iOS14 WidgetKit小试牛刀-1

Lrdcq , 2020/06/28 02:49 , 程序 , 閱讀(4721) , Via 本站原創
iOS14开放给开发者的大一统的小控件WidgetKit出来了。作为“抄袭安卓”的必不可少的一步。初探下苹果如何在偏向于安卓的高自由度,高可配,和自己风格的安全,隐私,高性能之间作出平衡,最终设计出的这个WidgetKit呢。

概览

點擊在新視窗中瀏覽此圖片

以上是官网的图,在工程里,小控件作为一个Widget Extension存在(这次另一个大功能App Clips则是一个独立的App,只是必须依附于主app提审而已)。新建出来标准的demo工程,我们就注意到几个关键点:

a. WidgetKit完全依托于SwiftUI建设的,从头文件上看甚至可以说WidgetKit是SwiftUI的一个拓展包而已。(Widget和相关的WidgetConfiguration之类的协议直接就在SwiftUI库内)。看起来苹果爸爸推SwiftUI还是有一手的。当然,同时这给我们各种老代码迁移带来了不少的坑,还好WidgetKit是iOS14 only,旧版系统应该苹果不会下发。

b. 一个WidgetConfiguration,核心配置了啥:kind标识符,无所谓;provider提供空间刷新时机和刷新内容的对象,其实就是控件内容提供者;placeholder一个默认的控件view;content一个获取实际渲染view的block(入参数是provider生成的内容)。明显,核心逻辑,即内容生产provider,和控件渲染content从底层就是完全分离的。意味着我们的程序其实是无法自由控制整小控件的,当然交互什么的就别想了。考虑到provider提供的内容还有触发时机甚至意图(就是SiriKit使用那种意图Intent),明显小控件的触发苹果系统收口了。

c. 以上信息也可以看出来,苹果家的小控件并不是像安卓一样自由发挥的东西,看起来更像是一个view生成并且截了一个快照贴到了桌面上。点击这个控件事实上也是打开app而已,控件本身不提供交互功能。这就是苹果的收口方式把——这样确实可以大大的保障app桌面常驻性能的稳定。

d. 整个demo项目的写法已经完全迁移到新版的SwiftUI上,类似于
@main
struct LRDWidget: Widget {
    public var body: some WidgetConfiguration {
        StaticConfiguration(
                    kind: "key",
                    provider: LRDProvider(),
                    placeholder: LRDPlaceholderView()
                ) { entry in
                    LRDEntryView(entry: entry)
                }
                .configurationDisplayName("configurationDisplayName")
                .description("description")
    }
}

全用上了新功能,并且也没有别的推荐写法提供(我们甚至不知道这个main函数展开应该是个啥,其实并不是mian函数,而只是一个叫main的生命周期方法)。只能喜大普奔了。

e. 整个WidgetKit的可用性都是**@available**(**iOS** 14.0, **OSX** 10.16, *),OSX喜大普奔。

小控件注册

小控件注册的形式与提供的基本信息上面已经提到了。注意到其实WidgetConfiguration本身无法直接使用,目前有两个子类去使用:

a. StaticConfiguration,看名字是静态小控件配置,其实是普通小控件,没什么意外都用它。

b. IntentConfiguration,带siri意图的小控件,生产信息的时候会携带当前匹配的用户意图。如果以上文字看不懂在说啥的话,官方举的例子是,比如天气app,你告诉siri我要看北京的天气,天气app响应这个intent的同时,小控件也可以做相应的行为。即可以通过intent去触发小控件的刷新。

默认的demo建出来是一个IntentConfiguration的代码,不过我们还是从普通的StaticConfiguration看起。毕竟这两者的区别只是数据提供的provider触发时机不同。

除了核心信息,可以进行的配置还包括:

- configurationDisplayName:即控件菜单里展示名字
- description:控件菜单里的描述
- supportedFamilies:控件支持的大小,即超大,大,中三种
- onBackgroundURLSessionEvents这个就有意思了。小控件的provider本来是同步的,如果涉及到网络请求,得采用类似于后台fetch的方案,信息会通过onBackgroundURLSessionEvents这个东西回来,再触发刷新,这个过程可以把请求的数据给存起来,重新渲染时再使用。

内容提供

普通控件的内容提供是一个TimelineProvider,与其说是内容提供,它提供的其实是接下来一个序列的刷新时刻和每个时刻刷新的内容。在核心的timeline方法里,我们需要返回一个数组,里面全是一堆TimelineEntry,包含了每一个刷新的tick时间和我们实际业务信息附加数据。比如
struct LRDInfoEntry: TimelineEntry {
    var date: Date
    var info: String
}

struct LRDProvider: TimelineProvider {
    typealias Entry = LRDInfoEntry

    func timeline(with context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        let entry_a: LRDInfoEntry = LRDInfoEntry(date: Date().addingTimeInterval(00.0), info: "Hello lrdcq a")
        let entry_b: LRDInfoEntry = LRDInfoEntry(date: Date().addingTimeInterval(10.0), info: "Hello lrdcq b")
        let entry_c: LRDInfoEntry = LRDInfoEntry(date: Date().addingTimeInterval(20.0), info: "Hello lrdcq c")
        let entry_d: LRDInfoEntry = LRDInfoEntry(date: Date().addingTimeInterval(30.0), info: "Hello lrdcq d")
        completion(Timeline(entries: [entry_a, entry_b, entry_c, entry_d], policy: .never))
    }
}

我定义了LRDInfoEntry,核心数据dete和附加信息info字符串,并且在timeline方法返回的数组里给了4个家伙,时间分别是当前,10秒后,20秒后和30秒后,那我们的小控件将在这几个时机触发刷新和rerender。

要注意的是,我们同时还给了个policy表示这个这次给了timeline后下次啥时候再来一次timeline检查。分别是atEnd即数字展示完之后重新检查;after(date: Date)固定一定时间后再检查,never不再检查了(有别的程序上的方式可以重新触发)。不过试了一下,如果不小心实现为了高速无限循环刷新,系统会把这个timeline给干掉,具体规则和限制不明。

官方也给了清晰的时间轴图,如下:
點擊在新視窗中瀏覽此圖片

其他不重要的东西

渲染:使用SwiftUI写界面,大家加油吧。

手工触发:在主app中可以手工触发小控件刷新或者修改小控件的配置,通过WidgetCenter触发,既然在app里,小控件当然想怎么改都行咯。

智能卡片:在TimelineEntry可选的提供一个relevance即TimelineEntryRelevance对象来描述当前卡片内容的价值/分数,用于苹果的智能卡片。这个就很高级了。可惜目前模拟器智能卡片跑不动。

小控件组:更常见的场景,入口@main标记在在一个WidgetBundle上,类似于如下提供一组小控件。
@main
struct LRDWidgetsBundle: WidgetBundle {
   @WidgetBundleBuilder
   var body: some Widget {
        LRDWidgetBig()
        LRDWidgetSmall()
   }
}

学习资料:目前虽然还不是很丰富,但是已经足够了:https://developer.apple.com/documentation/widgetkit
关键词:swift , ios14 , widgetkit
logo