手把手教你创建widget2
接上篇iOS Widget,这里介绍下WidgetBundle的用法和怎么做一个支付宝类似的 widget,上篇里把WidgetBundle
写成了WidgetGroup
,我的错。
WidgetBundle 的用法
再来回顾一下什么情况下使用 WidgetBundle
,上篇里介绍了supportedFamilies
,可以设置Widget 不同的尺寸,比如Small
、Meidum
、Large
等,但是如果想要多个同尺寸的 Widget ,比如:想要两个Small
尺寸的 Widget ,类似于下面东方财富 Widget 的效果,就需要用WidgetBundle
,设置多个Widget
。
WidgetBundle的使用不难,下面来看下,上篇最后的代码(可以去https://github.com/mokong/WidgetAllInOne下载,打开 Tutorial2),只显示了一个 Medium 尺寸的 Widget,这里修改为使用WidgetBundle
显示两个Medium
尺寸的 Widget。
新建SwiftUIView,命名为WidgetBundleDemo
,步骤如下:
- 导入WidgetKit
- 修改main入口为WidgetBundleDemo
- 修改WidgetBundleDemo类型为WidgetBundle
- 修改body类型为Widget
代码如下:
1 |
|
然后编译运行,failed,报错是xxx... error: 'main' attribute can only apply to one type in a module
,意思是,一个module中只有有一个@main,标记程序入口,所以需要移除多余的@main,那哪里有呢,在DemoWidget.swift中,因为之前main入口是DemoWidget,而现在的main入口是上面新建的WidgetBundleDemo,所以需要移除DemoWidget中的@main,移除后再次运行查看效果,发现添加Widget的预览中出现两个一模一样的Medium尺寸的Widget。
Wait,上篇里说过,不同的Widget左右滑动的时候,上面的title和desc也是会跟着滑,为什么这里没有跟着滑?
确实是,嗯,应该是标题和内容一样的原因,一起来验证下,首先在DemoWidget中添加title和desc的属性,如下:
1 |
|
然后修改引用DemoWidget的地方,即WidgetBundleDemo类中,传入不同的标题和描述,如下:
1 |
|
再次运行,查看效果,就会发现title和desc也移动了,效果如下:
很简单是不是,WidgetBundle
的使用就是上面的用法,但是这里需要说明一点,WidgetBundle
中放的都是Widget
,而每个Widget
都有自己Entry
和Provider
,即:WidgetBundle
中的每个Widget
都需要实现类似DemoWidget
的方法和内容。
创建一个支付宝Widget的组件
然后来实现如下支付宝小组件的效果:
UI实现
从第一张图开始,先来拆分结构,分为左右两个view,左边view是日历+天气,右边是4个功能入口,整体是一个medium尺寸的,然后来实现:
左边的view代码如下:
1 |
再来看右侧4个功能入口,再创建入口之前,先来考虑一下创建入口对应的Item,这个Item要有哪些字段?显示需要图片和标题,点击后跳转需要链接,另外SwiftUI中forEach遍历需要id。
然后再看下支付宝widget,长按 -> 编辑小组件 -> 选择功能,能看到所有可选的功能,所以这里需要定义一个type,用于枚举所有的功能,这里仅以8个来示例。资源文件放在AlipayWidgetImages
文件夹下。
所以功能入口对应的单个item整体定义如下:
1 |
|
然后来看右半边按钮组的实现,创建AlipayWidgetGroupButtons.swift
,用于封装展示4个按钮的view,代码如下:
1 |
|
然后创建左半边的view,分为三个部分,天气、日期、和提示条,其中提示条单独封装。代码如下:
提示条view:
1 |
|
左半边view整体:
1 |
|
最后把左半边view和右半边的按钮组结合起来,代码如下:
1 |
|
其中定义的AliPayWidgetMediumItem,是类似于VM,将model转为view需要的数据输出,代码如下:
1 |
|
然后创建入口和Provider,代码如下:
1 |
|
最后在WidgetBundle中使用,如下:
1 |
|
最终显示效果如下:
Widget Intent的使用
Static Intent Configuration
接着上面的来看,对比支付宝widget,可以看到支付宝widget长按后会出现编辑小组件
的入口,而上面实现的没有,下面就来看下如何实现这个的显示。
编辑小组件
入口的出现,需要创建Intent,然后CMD+N
新建,搜索intent
,如下图,点击下一步
然后输入名字,需注意的是这里的target要主Target和Widget Target
都要勾选,点击Create
打开新建的WidgetIntents,里面目前是空白,点击左下角的+
,如下图
可以看到,有4个按钮可供选择,分别是New Intent
、Customize System Intent
、New Enum
、New Type
。这里选择New Intent
。
Ps: 几个入口中
Customize System Intent
不常用,New Intent
几乎是必须要添加的;New Enum
是新建一个枚举,这个枚举和代码中的枚举名字不能相同,所以使用时需要转换;New Type
新建一个类,后面会有示范。
点击New Intent
后,需要注意几个方面:
- Intent的名字需要修改,因为默认为
Intent
,而项目中可能有不止一个Intent
文件,所以需要修改命名,修改命名时要注意的是在项目中使用时,会自动在修改的名字后面添加Intent
,比如修改为XXX
,项目中使用时的名字是XXXIntent
,所以要注意不要重复 - 然后是Intent的
Category
,这里修改为View
,其他几个类型,感兴趣的可以一一尝试,下面的title
也修改为文件的名字 - 再然后是下面内容的勾选,默认勾选了
Configurable in Shortcuts
和Suggestions
,这里取消勾选这两个,改为勾选Widgets
,意义很好理解。勾选的越多要设置的就越多,所以刚开始只需要勾选Widgets
就够了,后面熟悉了,想要设置Siri建议
或者快捷指令
,再来勾选另外两个,尝试设置。
然后再来点击左下角的+
,新增一个Enum
,要注意的是Enum的类名不能和项目中Enum的名字一样,Enum是用来选择,点击编辑小组件后进行选择的,所以Enum中的内容是根据实际来定义的,添加case的displayName可以为中文,在这里就是和项目中ButtonType
的内容一致,如下图。
Enum新增好了之后,再点击刚刚创建的StaticConfiguration
,在Parameter部分点击新增,然后命名为btnType
,修改Type为创建的Enum类型,取消勾选Resolvable
,如下:
至此,Intent添加完成,运行,查看效果,发现,依旧没有编辑小组件
入口,为啥呢?
虽然创建了Intent,但是并没有使用Intent的小组件,所以需要新增一个使用Intent的小组件,步骤如下:
新建StaticIntentWidgetProvider
类,其中代码如下:
1 |
|
在WidgetBundle
中添加显示,如下:
1 |
|
运行查看效果如下:
备注:
如果运行后,出现了编辑小组件
,但是点击后,编辑界面为空,没有显示上面步骤二和三的图片,可以查看Intent是否勾选到主项目,如下:
Dynamic Intent Configuration
继续对比支付宝的Widget,可以看到上面的实现的Static Intent Configuration
样式和支付宝的并不相同,支付宝的展示了多个,且每个点击选择的样式也和上面实现的样式不同,所以是怎么实现的呢?
答案是Dynamic Intent Configuration
,接着往下看:
选中Intent,点击添加New Intent
,命名为DynamicConfiguration
,修改Category为View
,勾选Widgets
,取消勾选Configurable in Shortcuts
和Suggestions
,如下:
继续,点击添加New Type
,命名为CustomButtonItem
,用于DynamicConfiguration
中添加Parameter
时使用。在Properties
中添加urlStr
和imageName
属性为String类型,再添加buttonType
属性是定义的Enum类型——ConfigrationButtonType,如下:
然后,为DynamicConfiguration
添加Parameter
,选择Type为CustomButtonItem
,勾选Supports multiple values
、Fixed Size
、Dynamic Options
,取消勾选Resolvable
,在Dynamic Options
下的Prompt Label
中输入文案请选择
,Fixed Size`中不同样式下的Size可修改。如下:
到这里,Intent中的设置已经完成了,但是还有个问题,虽然Intent中勾选了Supports multiple values
,数据从哪里来,点击编辑小组件后,默认展示的几个数据是哪里来的?点击单个按钮时,跳转后展示的所有的数据是哪里来的?
答案是Intent Extension
,点击File -> New -> Target,这里注意,这个是Target,搜索Intent
,选择Intent Extension
,如下,点击下一步,取消勾选Includes UI Extension
,点击完成,如下:
然后,选中.intentdefinition
文件,Target MemberShip
中把刚刚创建的Target也勾选上,如下图:
再然后,选中项目,选中WidgetIntentExtension
Target,修改Deployment Info
为15.0
,在Supported Intents
中点击+
,然后输入DynamicConfigurationIntent
,如下:
由于Intent Extension
中要使用Widget中的ButtonType
,所以选中ButtonType
所在的类,在Target MemberShip
中勾选Intent Extension
的Target,如下:
然后选中IntentHandler
,这里面就是数据来源的地方,修改内容如下:
1 |
|
最后,创建新的IntentTimelineProvider
,来显示这个效果,代码如下:
1 |
|
效果如下:
到此差不多就完成了,对比支付宝widget,可以看到,还有展示天气
和选择功能位置
的样式,在DynamicConfiguration
的Parameter
中,直接添加两个属性,选择功能位置
为Enum
类型,展示天气
为Bool
类型,然后调整位置,把selectButtons
属性移到最下方,详细步骤大家自己尝试一下。
最终效果如下:
总结:
完整项目代码已放在github: https://github.com/mokong/WidgetAllInOne
补充:
如果想要刷新widget,widget默认刷新时机是根据timiline设置来的,但是如果想要强制刷新,比如在APP中操作了,状态发生了改变,想要widget里吗刷新,可以用如下代码,在触发刷新的地方调用即可:
1 |
|