Jun 30

iOS14 WidgetKit小试牛刀-用户配置与intent

Lrdcq , 2020/06/30 01:43 , 程序 , 閱讀(5065) , Via 本站原創
上一话http://lrdcq.com/me/read.php/106.htmWidgetKit是从StaticConfiguration入手的,而避开了实际上应该更常用的IntentConfiguration。而理解在WidgetKit中出现的Intent,显然不能按照Siri中的Intent去理解了。在WidgetKit中,Intent更多的是用来呈现长按配置菜单中的选项,简单的来说,Intent是一个配置表。如下图:

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

这个LRDConfigurationIntent的内容可以理解为,这个自定义Intent是一个Widget展示用的配置(就是钩上的那个),其中有一个参数,叫Mode,这个Mode的类型是下面的自定义类型LRDMode,并且标明内容是动态生成。LRDMode的定义如下:

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

除了默认参数,我们新增了一个参数为name。顺带一提,这个场景下intent的response就完全没意义了。

intent用法

以上配置是官方文档说明中描述的最复杂的配置,其中LRDConfigurationIntent的参数其实就是Widget的配置项目了,这里我们的参数是一个自定义的type,不过正常情况下内置类型,如下:

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

就可以应付绝大部分的场景了。

自定义type也分为固定的和动态生成的。一般来说固定的,也就是在这个配置里直接写明可选的枚举就可以解决了,如上进行动态生成的场景也是少数中的少数。因此我们按照最罕见的场景来尝试。

完成动态intent选项的配置,我们需要新建一个Intents Extension去承接LRDConfigurationIntent的处理。刚才在intent配置里编写的那些东西,Xcode会自动生成代码,主要是
@objc(LRDConfigurationIntent)
public class LRDConfigurationIntent: INIntent {

    @NSManaged public var Mode: LRDMode?

}
@objc(LRDMode)
public class LRDMode: INObject {

    @available(iOS 13.0, macOS 10.16, watchOS 6.0, *)
    @NSManaged public var name: String?

}

和其对应的LRDConfigurationIntentHandling之类的协议,我们的IntentHandler主要就是实现相关东西。

要提供LRDMode的可选项,主要是实现协议中provide{Mode}OptionsCollection这个方法,这个方法其实就是通过程序,异步丢一堆INObject实例进去作为枚举项目,简单的用法看起来如下:
    func provideModeOptionsCollection(for intent: LRDConfigurationIntent, with completion: @escaping (INObjectCollection<LRDMode>?, Error?) -> Void) {
        //直接构成三个数据
        let mode_a = LRDMode(identifier:"mode_a", display: "mode_a");
        mode_a.name = "a模式"//自定义添加的属性不会自动生成构造器,比较麻烦啦
        let mode_b = LRDMode(identifier:"mode_b", display: "mode_b");
        mode_b.name = "b模式"
        let mode_c = LRDMode(identifier:"mode_c", display: "mode_c");
        mode_c.name = "c模式"
        //构成INObjectCollection,completion出去
        let collection = INObjectCollection(items: [mode_a, mode_b, mode_c])
        completion(collection, nil)
    }

IntentConfiguration使用

使用IntentConfiguration,和StaticConfiguration相比,其实就是多传了一个Intent进入:
struct LRDWidget: Widget {
    public var body: some WidgetConfiguration {
        IntentConfiguration(
                    kind: "key",
                    intent: LRDConfigurationIntent.self,//这个
                    provider: LRDProvider(),
                    placeholder: LRDPlaceholderView()
                ) { entry in
                    LRDEntryView(entry: entry)
                }
                .configurationDisplayName("configurationDisplayName")
                .description("descriptions")
    }
}

然后提供的LRDProvider不再是实现于TimelineProvider,而是一个类似的东西IntentTimelineProvider。这个协议上的差别是,入参多出了LRDConfigurationIntent本身,因此处理的时候可以直接获取到LRDConfigurationIntent对象进行读取,类似于:
    func timeline(for configuration: LRDConfigurationIntent, with context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        let name = configuration.Mode?.name ?? "";
        //then do something....
    }

其他

这个调试过程远比之前复杂很多,注意几点:

a. Intent配置文件修改后重新生成相关源码有延迟的,大概率需要重新build一下,类似于安卓修改了gradle脚本后要rebuild一样。千万别忘了导致写错代码。

b. 同样,由于目前整个程序出现了三部分了(主app,widget ex,intents ex),从数据提供的角度,任何修改都必须先run app,再run intents ex,再run widget ex才对,否则还是容易出岔子。

c. 就算用简单配置不存在Intents Extension,Intent配置Widget相关逻辑也不能在模拟器上运行,必须真机调试。给苹果一个大写的服。

d. 苹果为何Widget的用户配置要通过Intent这种蛋疼的方式来实现,可以想象一下:大部分去实现Widget的开发者,都是深度苹果开发者(比如苹果自己的系统应用开发者),他们的应用本身就大量依赖Intent完成大量用户可定制的内容。那么将相关配置继承下来让Widget可以继续使用,也是很自然的了。

学习资料:以上内容还是从学习资料实践而来
https://developer.apple.com/documentation/widgetkit
关键词:ios14 , widgetkit , swift
logo