|
iOS14 动态配置Widget开发与实践
iOS14 动态配置Widget开发与实践
金莹莹@贝壳找房
贝壳产品技术
贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容
2021年02月24日 00:29
前言我们知道苹果对于Widget表现的足够克制,导致iOS的Widget的交互能力很弱,只能展示静态的东西,甚至连滚动列表、输入文字都不可以。虽然可以通过时间线来做部分更新逻辑,但是对于用户来说还是静态的,而且一种Widget可以重复添加,如果用户不能自定义配置,这种可以重复添加的功能也就失去了意义。根据服务端状态来提供可配置Widget,可以带给用户更好的服务和更多元的交互体验。我们之前的文章(iOS14新特性-WidgetKit开发与实践)介绍了Widget的一些特性,以及如何创建静态的Widget(StaticConfiguration)。区别于上一篇文章,我们这次主要讲一下如何实现动态配的Widget,以及可能遇到的一些问题。1 动态配置组件为了节省开发时间,网络部分桥接了主工程网络组件,UI上简单实现了一下主工程里九宫格的效果,美观上还有待调整。此次主要实现动态可配置。先看下目前实现的动态配置小组件的Demo效果:编辑状态下:左:Small,右:Medium以下是选择配置项的页面:左:静态配置,数据是本地写死的,更新的话,需要发版。右:按照服务端接口动态配置的可选项创建动态配置的小组件,分为两种情况:新建Widget扩展的时候勾选了Include Configuration Intent,系统会自动创建出与Widget扩展名称相同的xxx.intentdefinition配置文件,并且创建了一个可以供Widget使用的自定义意图Configuration,Widget文件中的Provider、SimpleEntry、WidgetConfiguration等都自动做了相应的配置,可以直接用。现有的静态的Widget,改成可配置的。第一种自动化程度比较高,新手做也不容易出错。下边我们着重讲一下第二种情况。静态Widget改成动态配置的小组件三个关键步骤:(1)创建SiriKit Intent文件(2)创建 Intents Extension 意图扩展(3)修改Widget文件1.1 创建SiriKit Intent文件1.1.1 在工程目录中, 新建文件,选择SiriKit Intent Definition File,记得勾选member of the Target1.1.2 点击新建的xxx.intentdefinition文件,Xcode显示了一个空的意图定义编辑器,点击+添加一个自定义意图(Custom Intent),选择New Intent,命名为LJZDMultibleConfigurationIntent,记住这个名称,很重要!1.1.3 将Category设置为View,并选择“Intent is eligible for widgets”复选框,以表明小部件可以使用该Intent。注意:注意自定义意图的名称 LJZDMultibleConfigurationIntent,Xcode编译的时候,会自动生成相关的自定义意图类相关的代码。后边引用的自定义意图的类名、协议都是根据这个名称生成的。不是根据xxx.intentdefinition文件名生成的。1.1.4 在Parameters下,点击+添加参数,这里分为三大类:System Types、Enums、Types。其中:Enums:枚举类型,提供静态的可选项,可选项数据是提前写好的,要修改需要发版。Types:提供动态类型的可选项,可以从接口获取数据。选择Types类型之后,意图定义编辑器里会在CUSTOM INTENTS下边新增一个模块:TYPES。Types里默认有两个参数Identifier,displayString,可以按需增加新的属性。苹果的Code alone里没有增加额外属性,它是通过`Identifier`来找到具体的数据模型。我这里新增了Widget需要的icon地址、跳转主App具体页面的路由地址、是否可用等属性,满足了UI和跳转的逻辑。接口请求回来,直接创建对应的可选项意图模型就可以了。以上就是配置意图文件的部分。注意:意图文件创建成功后,编译工程,会自动生成一些代码,后边使用意图的过程中,会用到这些代码1.2 创建Intents Extension创建动态配置的Widget第二步,新增一个IntentsExtension的target。关键步骤:1.2.1 在Intents扩展的target里,找到Supported Intents配置,增加前面配置的自定义意图名称LJZDMultibleConfigurationIntent。1.2.2 工程目录里找到xxx.intentdefinition文件,勾选文件的Target Membership,把xxx.intentdefinition文件添加到IntentExtension的target里。重要:一定要在File inspector中,验证intent定义文件,也就是 xxx.intentdefinition 文件包含在应用程序、小部件扩展和intent扩展里1.2.3 在Xcode中添加了名称为IntentHandler的意图扩展后,工程会创建一个IntentHandler.swift的文件,生成一个IntentHandler类,里面提供了处理数据的handler,在这里需要实现LJZDMultibleConfigurationIntent类的LJZDMultibleConfigurationIntentHandling协议方法,这个方法里去做获取配置数据的操作。下边是LJZDMultibleConfigurationIntentHandling协议的部分代码,这些代码是添加自定义意图后,编译工程才会生成。也可以找到自定义意图文件LJZDMultibleConfiguration:直接去Xcode生成的LJZDMultibleConfigurationIntent文件中查完整代码:@available(iOS12.0,macOS10.16,watchOS5.0,*)@available(tvOS,unavailable)@objc(LJZDMultibleConfigurationIntentHandling)publicprotocolLJZDMultibleConfigurationIntentHandling:NSObjectProtocol{/*!@abstractDynamicoptionsmethods-provideoptionsfortheparameteratruntime@discussionCalledtoquerydynamicoptionsfortheparameterandthisintentinitscurrentform.@paramintentTheinputintent@paramcompletionTheresponseblockcontainsoptionsfortheparameter*/@available(iOS14.0,macOS10.16,watchOS7.0,*)@objc(provideMultChannelsOptionsCollectionForLJZDMultibleConfiguration:withCompletionfuncprovideMultChannelsOptionsCollection(forintentJZDMultibleConfigurationIntent,withcompletionescaping(INObjectCollection,Error)->Swift.Void)1.3 修改Widget文件首先我们对比实现Widget的一组结构体的关键协议:协议WidgetConfigurationTimelineProvidergetSnapshotgetTimeline静态 WidgetStaticConfigurationTimelineProviderfunc getSnapshot(in context: Self.Context, completion: @escaping (Self.Entry) -> Void)func getTimeline(in context: Self.Context, completion: @escaping (Timeline) -> Void)可配置 WidgetIntentConfigurationIntentTimelineProviderfunc getSnapshot(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Self.Entry) -> Void)func getTimeline(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Timeline) -> Void)把静态Widget修改成可配置的Widget:修改Widget入口函数的Configuration,StaticConfiguration->IntentConfiguration修改TimelineProvider协议TimelineProvider->IntentTimelineProvider修改对应协议的必要方法按照顺序修改对应的协议及方法,就可以正常跑起来了。至此,静态Widget修改成可选配置Widget就算完成了!2 SwiftUI 提供视图苹果要求,Widget的视图只能通过SwiftUI编写,但是也不是所有的SwiftUI都能用。更不能使用UIViewRepresentable或者NSViewRepresentable包装的 UIKit 和 AppKit 视图。具体可以使用的SwiftUI视图,可以参考苹果的开发文档Present your app’s content in widgets with SwiftUI views(https://developer.apple.com/documentation/widgetkit/swiftui-views)2.1 加载图片单独说一下Widget里加载图片的问题,我们先看下SwiftUI文档中Image加载图片的方式:这里没有直接从网络获取图片的方式,只能用UIImage去加载网络图片,然后通过init(uiImage: UIImage)的方式创建 Image 平时我们开发中利用UIImage加载网络图片都是异步操作,给imageView一个占位图,然后去异步请求图片信息,接口请求成功,再把图片数据渲染回去。但是到了Widget这里是行不通的,只能同步加载图片。获取配置数据的流程如上图,同步处理图片的操作需要在渲染视图之前,于是就放到了getTimeline的方法中,icon图片较小,实际体验速度还是挺快的。2.2 响应用户操作当用户点击Widget的时候,可以直接跳转到主App的某个相关页面。苹果提供了两种方式:widgetURLhttps://developer.apple.com/documentation/swiftui/view/widgeturl(_Linkhttps://developer.apple.com/documentation/swiftui/link* For all widgets, add the widgetURL(_ view modifier to a view in your widget’s view hierarchy.If the widget’s view hierarchy includes more than one widgetURL modifier, the behavior is undefined.* For widgets using systemMedium or systemLarge, add one or more Link controls to your widget’s view hierarchy.You can use both widgetURL and Link controls. If the interaction targets a Link control, the system uses the URL in that control.For interactions anywhere else in the widget, the system uses the URL specified in the widgetURL view modifier.说明一下主要区别:一个widget视图,只能有一个widgetURL,但是可以有多个Link控件。如果一个Widget视图内,多个控件都设置了widgetURL,以最后一个设置的为准。small类型的Widget也是可以添加Link控件的,至于苹果为什么没有提到,我的理解是small类型的Widget已经很小了,不建议做复杂的视图展示,使用一个widgetURL完全能满足使用。2.3 主App处理跳转Widget通过widgetURL、Link控件打开主App,系统都会把URL传递到onOpenURL(perform, application(_penptions方法或者application(_pen方法。如果两者没有使用,系统会传递一个NSUserActivity对象给onContinueUserActivity(_:perform,application(_:continue:restorationHandler, 或者application(_:continue:restorationHandler来响应。NSUserActivity对象包含了与用户交互的Widget的详细信息,如果Widget是可配置的,也会包含自定义意图的配置信息。如果主App是用Swift写的,用WidgetCenter.UserInfoKey来获取,如果是OC写的,用WGWidgetUserInfoKeyKind和WGWidgetUserInfoKeyFamily来获取。3 共享数据Extension程序跟主App进行数据通信,需要配置好App Groups,通信方式如下:NSUserDefaultshttps://developer.apple.com/documentation/foundation/nsuserdefaults/1409957-initwithsuitenamelanguage=objcNSFileManagerhttps://developer.apple.com/documentation/foundation/nsfilemanager/1412643-containerurlforsecurityapplicatilanguage=objcKeyChain Sharinghttps://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_appslanguage=objc具体使用区别算是老生常谈的问题了,这里就不再赘述了。具体用法可以点击跳转到官方文档查看。4 调试选择Widget-extension对应的target,Xcode运行,就可以把小组件会直接添加到设备的桌面上。但是如果你的supportedFamilies没有指定哪一种的话,你是没有办法debug的。或者当你的Widget-extension使用WidgetBundle支持多种Widget的时候,运行不起来,因为系统并不知道你要调试哪一种Widget。我们可以通过设置Widget-extension的环境变量来解决这个问题。4.1 环境变量找到Widget-extension的Edit Scheme,添加环境变量。_XCWidgetKind对应的WidgetBundle里的需要调试的Widget_XCWidgetFamily对应Widget的尺寸,可以设置为samll,medium,large_XCWidgetDefaultView对应Widget显示的默认视图,可以设置为timeline,snapshot,placeholder4.2 Attach To Process运行主App的时候,也可以选择Attach To Process的方式调试扩展程序。Xcode菜单栏选择Debug->Attach To Process by Pid or Name,然后填上需要调试的扩展的Targe名称5 可能遇到的问题Configuration Intent找不到的问题。这个问题很有迷惑性,在实践的过程中,偶尔发生。新建工程,勾选Configuration Intent并没有什么问题,有问题是现有工程的接入:不可配置的Widget修改成可配置现有工程新建可配置Widget,新增、修改Intent针对静态Widget改成可配置的问题,前文已经提到了。现有的OC工程接入可配置Widget,关键点还是OC-Swift的混编,添加Bridging-Header文件后,就找不到Intent了。在Intent配置页面,配置好Intent之后,编译一下,系统会自动生成XXXIntent类,继承自INIntent。这里有两个关键点:1. Intent类自动生成器Intent类是xcode自动生成的,自动生成代码,需要找到生成规则,增加OC-Swift混编头文件后,系统默认的生成代码语言为Automatic,Widget只支持Swift方式,在WidgetExtension的Target下,BuildSettings找到Intent Definition Compiler,指定语言为Swift。2. Intent命名规则比如名称是LJHotkeyConfiguration,自动生成的类名是LJHotkeyConfigurationIntent如果使用过程中,还是无法定位到自动生成的类文件,需要使用重启大法,关闭工程,重新打开就可以了工程重新打开后,颜色也正常了,也可以定位到类文件6 总结以上是实现一个Widget需要用到的结构体、协议以及他们之间的依赖关系。小组件功能简单,也足够“小”,但是涉及的知识点还是挺多的。希望此文对大家有帮助。参考资料Building Widgets SampleCodehttps://developer.apple.com/documentation/widgetkit/building_widgets_using_widgetkit_and_swiftui开发文档https://developer.apple.com/documentation/swiftui/widgetWidgets Code-along, part 1: The adventure beginshttps://developer.apple.com/videos/play/wwdc2020/10034Widgets Code-along, part 2: Alternate timelineshttps://developer.apple.com/videos/play/wwdc2020/10035Widgets Code-along, part 3: Advancing timelineshttps://developer.apple.com/videos/play/wwdc2020/10036Add configuration and intelligence to your widgetshttps://developer.apple.com/videos/play/wwdc2020/10194
预览时标签不可点
移动端37大前端69移动端 · 目录#移动端上一篇贝壳找房iOS启动优化实践下一篇一种按照library的维度进行Android包大小分析的方法和实践关闭更多小程序广告搜索「undefined」网络结果
|
|