目录
g. 可配置的Widget组件(详见WidgetStudy项目)
项目应用示例
技能点
SwiftUI语言
Widget小组件配置
App组之间的数据共享
Widget背调
a. 设计定位
用户可以通过 Widget 对主屏幕进行个性化定制,但是 iOS 14 的 Widget 跟其他系统上的小组件有很大的区别。在 Widget 的设计上苹果也保持了一贯的克制,定位于轻量化、仅用作关键信息的展示。比如系统自带 Widget 中的股票、天气、电量、运动信息,他们的共同特征是更新频率高、提供的信息重要,让用户不用打开 app 就可以浏览关心的内容。苹果也不希望开发者将 Widget 仅仅当作 app 的一个快捷入口,这样的需求更适合用 contextual menu 来实现。
我们想给经销商app的增加多元的入口体验,于是就有了这个需求。
b. Widget小组件限制
苹果基于上面的设计定位,同时也为了节省系统资源保证续航,对 Widget 的做了一些限制:
1、不支持动画,仅支持静态页面展示
2、更新频率由系统通过机器学习来动态分配
3、不支持拖拽、滚动等复杂的交互,不支持 Switch 等控件
c. Widget小组件 开发须知
1、只能使用SwiftUI进行开发,所以需要SwiftUI和Swift基础
2、Widget只支持3种尺寸systemSmall(2x2)、 systemMedium(4x2)、 systemLarge(4x4)
3、默认点击Widget打开主应用程序
4、需要在项目中进行App Groups的设置才能使其与主程序互通数据
官方设计文档: Widgets - System experiences - Components - Human Interface Guidelines - Design - Apple Developer
d. 什么是 SwiftUI
SwiftUI 于 2019 年度 WWDC 全球开发者大会上发布,它是基于 Swift 建立的声明式框架。该框架可以用于 watchOS、tvOS、macOS、iOS 等平台的应用开发,等于说统一了苹果生态圈的开发工具。
对于 Swift UI,官方的定义很好找到,也写得非常明确:
SwiftUI is a user interface toolkit that lets us design apps in a declarative way.
可以理解为 SwiftUI 就是⼀种描述式的构建 UI 的⽅式。
SwiftUI官方教程:Apple Developer Documentation
App Group 数据共享
Widget小组件属于一个独立的app,它有着和主项目完全不同的bundleId。
Widget小组件中接口请求需要与主项目相同的账号、dealerId、token等信息,于是就需要从一个app中读取另一个app的信息,但是这是不被苹果允许的,所以就有了APP group的概念,可以将同一个开发者账号下的app数据放到同一个缓存组中,这样这些app就可以共享这些信息。
a. 配置 App Groups
1、开发者账号配置,并更新pp证书
I. 创建两个APP,并且在Apple Developer中创建两个APP的id创建APPid时,在APP Groups复选框打上对钩,当我们配置完成App ID之后,会发现App Groups是Configurable状态,这是因为咱们还没有配置相应的app groups,别着急,咱们等会再来管它。
II. 在id那一组中又一个App Groups选项,我们新创建一个APP Groups,这里一共有两个选项,第一个是我们这个app group的描述,第二个是我们app group的id。这个id默认是要group.打头,并且是不能去掉的。
III. 现在去配置新创建的两个APP ID,因为再创建的时候勾选了APP Groups,但是并没有配置它,所以它回事黄色的状态,现在点击Edit,进入之后选择新创举的groups。
IV. 点击Edit---勾选上刚创建好的App Group----配置完成,在返回来看一下咱们的App ID,完美~Enabled状态了.
2、Xcode配置
主项目和Widget Extension添加App Groups
Capabilities->App Groups
当我们配置完以后,会在文件目录下多出来一个.entitlements的文件
b. 缓存数据共享-代码实现
1、文件存储
主项目写入数据
widget读取数据
2. 沙盒存储(参考WidgetGroupTool类)
写入数据
I. 传统方法
II. 新方法 (swift属性包装器)
读取数据
c. pod共享
d. 文件共享(详见WidgetGroupTool)
Widget小组件配置
a. 添加Widget
File -> New -> Target -> Widget Extension
Include Configuration Intent如果你所创建的Widget需要支持用户自定义配置属性,则需要勾选这个(例如天气组件,用户可以选择城市;记事本组件,用户记录信息等),不支持的话则不用勾选
会生成一个新文件夹,包含以下内容
- 扩展名.swift
- 扩展名.intentdefinition(勾选Include Configuration Intent)
- Assets.xcassets
- Info.plist
b. 编写Widget
1、原理:开发者通过 SwiftUI 构建 Views,定义Timelines为 Views 提供对应时间所需的数据,当数据变化时,通过reload更新数据。TimelineProvider提供一组TimelineEntry和ReloadPolicy,用来后续刷新页面。
2、实现 Widget 的代码相对比较模版,可以从 Widget 的入口开始,缺什么补什么。
I. 入口:@main
- @main:代表着Widget的主入口,系统从这里加载,可用于多Widget实现
- kind:字符串,唯一标识 Widget。
- WidgetConfiguration:有两类配置,分别为
- StaticConfiguration : 可以在不需要用户任何输入的情况下自行解析,可以在 Widget 的 App 中获取相关数据并发送给 Widget。
- IntentConfiguration:依赖于 App 的 Siri Intent,会自动接收这些 Intent 并用于更新 Widget,用于构建动态 Widget。
- .supportedFamilies:支持不同尺寸。
- description:添加编辑界面展示的描述内容
- configurationDisplayName:添加编辑界面展示的标题
II. Entry
渲染 Widget 所需的数据模型,需要遵守TimelineEntry协议。
III. Provider
遵守TimelineProvider协议,告诉 WidgetKit 何时渲染与刷新 Widget。需要实现以下 3 个方法
placeholder、getSnapshot、getTimeline
- getTimeline 是最重要的方法,后面的数据刷新都会在其中完成,所以可能会在其中完成最新的网络数据和本地数据的获取,然后转成 Model 以供使用。
- getTimeline 的方法里有一个 policy 参数,表示刷新的时机,可以选择.never(不刷新),.atEnd(Entry 显示完毕之后自动刷新) 或 .after(date)(到达某个特定时间后自动刷新)。
- Widget 刷新的时间由系统统一决定(有时候设置了也不会自己刷新),如果需要强制刷新 Widget,可以在 App 中使用 WidgetCenter 来重新加载所有时间线:WidgetCenter.shared.reloadAllTimelines()
IV. EntryView
屏幕上 Widget 显示的内容,可以针对不同尺寸的 Widget 设置不同的 View。
- Widget 能且只能使用 SwiftUI 构建界面。
- Widget 本质:一个随着时间线而更新的 SwiftUI View。
c. 多Widget组件实现
一个Widget只能实现大中小三个不同尺寸的组件形式,如果现有需求要做不同功能并且相同尺寸规格的组件则需要实现多组件。
通过修改原Widget入口文件方法添加更多配置来支持多个Widget
每个bundle body子属性上限为5个
d. 登录及授权
加载数据之前首先要解决的是授权问题。
1、主 app 在登录后将 token 等数据写入 app group 中
2、Widget 通过 HTTPS 接口加上 token 来访问用户数据。其中 token 通过 UserDefaults 来共享到 Widget,因为开发过程中不同的证书打包的 bundle id 不同,因此我们将 group name 设置成 group.[bundle id] 的形式,保证能正确读取 token。
Token 的同步有几个时机:
- App 启动后,更新token
- 登录成功,更新token
- 退出登录时,删除 token
e. 拉起app
苹果提供了两种 API 给到开发者,第一种是SwiftUI 的 WidgetURL API,它的可点击区域是在整个widget页面。
对于 systemSmall 类型来说,只支持 widgetURL 的方式,但是对于 systemMedium 和 systemLarge 还可以使用 SwiftUI Link API,而 Link 的可点击区域是这样的:
f. 埋点
Widget 的曝光事件我们是无法感知的,由于点击 Widget 会直接跳转到主 app,所以我们可以在跳转到主 app 的 URL 上增加埋点参数,主 app 解析 URL 中的参数来埋点。
触发网络请求的时候,也可以考虑添加埋点。
g. 可配置的Widget组件(详见WidgetStudy项目)
前面我们所介绍的构建小组件的方式,虽然可以通过时间线做部分更新逻辑,但对用户来说,依然是静态的。用户不能够根据自己的偏好对组件进行配置,还以天气类组件为例,有些用户可能关心的是空气质量,湿度等信息,有些用户可能只关心阴天雨天的信息,由于小组件的显示空间有限,有时候你无法将所有的信息都展示在组件内,因此让用户选择他感兴趣的信息进行小组件的配置非常重要。
首先,如果要让我们开发的Widget可以支持用户配置,需要在Widget的target工程中添加一个配置属性表文件,使用Xcode新建一个SiriKit Intent Definition File的文件,如下图所示:
之后,需要创建一个新的Intent配置,添加一系列的用户配置项,系统提供了各种类型的配置项,如让用户传入字符串信息的配置项,开关配置项,日期配置项等等,如下图所示:
重新运行Widget,我们的小组件就已支持用户配置功能,用户可以编辑小组件进行设置,当用户修改了配置项后,组件会重新请求Timeline时间线,在timeline回调方法中,会传入configuration对象,用来存储用户的配置信息
(添加枚举属性,修改display name)
需要注意以下几点:
- Intent 的 Category 选择为 View(即用于展示/配置 UI)
- 选中 Intent is eligible for widgets
- 取消选中 Siri can ask value for run(除非该 Intent 也用于 Siri Shortcuts)
同时需要修改Provider,改为遵循IntentTimelineProvider(详见Widget2)
h. 动态项配置(容易出错)
上面演示的这种配置方式,适用于当配置项固定的场景,更多时候,可能连配置项都是动态的,比如我们的应用会根据服务端的状态来提供不同的服务,这时可提供给用户开启的服务项目就是动态的。Widget的配置项也支持动态进行配置,这需要使用到Intents Extension的相关功能,下图为动态项配置
添加Intents Extension(官方文档)
IntentHandler类遵循对应的IntentHandling协议,并实现provideCardOptionsCollection,defaultCard协议方法
- provideCardOptionsCollection(for:with:)在用户点击 Widget 中 Card 配置项的时候,WidgetKit 会展示上图右侧中的列表 UI,其中的数据由这个方法异步返回。
- defaultCard(for:)我们可以通过实现该方法,在用户首次添加我们的 Widget 时,对于该 Widget 的某一个可配置项返回一个默认的参数值。例如在图示的实现中,我们返回了用户的主要信用卡(Primary Card)。
Tips:
- 通过使用 INObjectCollection(sections:) 构造器,传入 INObjectSection 数组,可以分区展示待选项列表。
- 自定义 Intent 类型继承自 INObject,通过重载/设置 displayString、 subtitleString 等属性,可以定制自定义类型在待选项列表中的显示内容。
- Intent Handling 协议中定义的 defaultXXX(for:) 方法被标记为 optional,但是依然推荐实现,因为一个好的默认视图对我们的 Widget 来说是十分重要的。
你可能注意到了待选项列表上方的搜索框。默认情况下,搜索框会对我们所返回的全部内容进行搜索过滤。但是,当待选数据较多,或者说待选数据取决于用户具体输入时,我们可以打开 Intent handler provides search results as the user types 选项,实现对待选项列表的实时更新。
在打开该选项后,Xcode 会为生成的 IntentHandling 协议的 provideCardOptionsCollection(for:with:) 方法添加一个 searchTerm 参数:
当用户在搜索框中输入字符时,WidgetKit 会调用该方法对待选项列表进行更新。首次显示待选项列表时,该参数值为 nil。
i. 配置页面颜色设置
背景色设置
文本按钮颜色配置
项目应用示例
上图为小组件的示例图
上图为添加小组件的步骤演示