目前负责的App新增了Widget功能,之后在组内分享中分享了下Widget的开发经验。基于之前的PPT提炼出了这篇文章。本篇文章只讲基于Widget关于iOS10+ 之后的知识点。
Widget是iOS8以后Apple推出的一项功能,并且在iOS10后进行了大幅的优化。
在主屏幕和锁定屏幕上向右滑动来访问Widget,也可以在对应的App图标上面使用3D Touch按压访问相应的Widget。
Widget设计规范和要求
Widget是一个单独的进程,和主App独立,但是支持数据共享。在设计和开发Widget时候要注意以下几点设计规范:
设计一个友好的交互体验
Widget用来执行非常简单的任务,尽可能提供点击一次就能完成的任务,Widget不支持窗口滚动,不支持键盘输入(其实是可以做到键盘输入的 具体办法见后面)
详见《App Extension Programming Guide》
要快速显示内容
内容要尽量从本地加载,依赖网络的内容要在本地做缓存,以免长时间等待。确保每次出现都有内容
提供充足的边距和填充
避免将内容扩展到Widegt边缘。每行最多显示4个按钮或图标
适应屏幕
iOS10以后,Widget支持折叠和展开。折叠状态下默认高度为110且不可更改。展开高度不超过一个屏幕的高度。(官方文档说最低高度为2.5个默认行高 443.5=110)官方推荐使用AutoLayout布局。
*横屏时候宽度还是默认屏幕宽度。不会拉伸**不要自定义背景色
系统自带模糊的背景色,尽量不要改(当然只是建议咯)。不要用照片做背景,会和壁纸冲突。
注意字体颜色、取一个好名字、一个App可以有多个Widget
字体颜色尽量是深色或者深灰色(然而用白色的最多)。如果一个App存在多个Widget,要命名清晰。Widget的名字里面,英文字母系统会自动转换成大写。
Logo会自动使用主App的icon
适当的时候让用户跳转到主App来做更多的事
Widget尽量只给用户提供简单的功能(规范而已。。),不要在Widget中出现“打开App”等按钮。点击Widget icon后会自动吊起主App。和主App交互使用URLScheme方法。后面会讲到。
很短的生命周期、要注意内存问题
离开屏幕2s+就会被销毁,后面会讲到
建立Widget Target
选择主工程,在Project设置界面下方点击加号,新建Today Extension
系统会自动生成TodayViewController和storyBoard。不要忘记在Target设置里面设置基本信息,版本号和主App保持一致,否则上传iTunes Connect会有警告邮件
也要注意选择Deployment Target。Xcode10默认是iOS12
和Widget共享代码
支持pod导入三方库,在podfile中新增Widget的target
Xcode10 后,如果在Build Phases中运行Script。执行pod可能报错。解决办法见
《#iOS知识小集# Xcode10 pod install 报错》主工程代码共享
在需要共享的类的.m文件中的Target Membership中勾选Widget所在的Target
Widget代码实现
NCWidgetProviding协议
Widget工程建立后会自动生成TodayViewController。
会遵循NCWidgetProviding协议
iOS10以后这个协议只有两个方法
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult result))completionHandler; |
其中widgetPerformUpdateWithCompletionHandler 默认返回NCUpdateResultNewData
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { |
这个可以忽略掉,直接返回NCUpdateResultNewData就好了
iOS10以后支持折叠和展开功能,折叠状态下默认高度为110且不可更改。展开高度不超过一个屏幕的高度。(官方文档说最低高度为2.5个默认行高 44*3.5=110)
在ViewDidLoaded方法中设置是否开启折叠功能
//NCWidgetDisplayModeCompact 收起模式 |
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize { |
使用纯代码
示例工程会默认使用StoryBoard,如果想使用纯代码。进行以下步骤
- 删除MainInterface.storyboard文件和NSExtensionMainStoryboard键值对
- 添加NSExtensionPrincipalClass为key ,value为TodayViewController
图片管理
Widget可以使用Asset Catalog管理图片,命名为Assets,和主工程使用方式一致
代码调试
在Widget工程更新代码后,可以运行主工程,然后添加Widget。就可以看到最新的效果展示。
如果想断点调试,要选择Widget Target
和主工程共享数据
Widget和主工程是完全独立的两个工程,两个独立的进程。所以数据共享是通过App Groups进行的。
App Groups需要去开发者中心去创建。ID必须以group开头。后面一般跟公司名称。
建立完成后回到主工程,打开App Groups开关,就能刷新出刚刚创建的Groups,打钩远中
然后把Widget Target 也打开App Groups,选中同一个Groups
App Groups可以通过NSUserDefaults和NSFileManager共享数据
- NSUserDefaults
//主工程中存 |
- NSFileManager
//存 |
App Gropu是跨App的,只要在同一个开发中账号。不同的App使用同一个Gropu ID都是可以共享数据的。在Shared目录下还有AppGroup目录。里面有各个Group ID的文件夹。其中通过NSUserDefault共享的数据在Library/Prefrences下。是一个plist文件。
Widget吊起主工程
Widget吊起主App通过URLSchemes
- 为主App设置URLSchemes
2.Widget添加交互[self.extensionContext openURL:[NSURL URLWithString:@"YDUDictionary://action=CameraTranslate"] completionHandler:^(BOOL success) {
NSLog(@"open url result:%d",success);
}]; - 主App中处理Scheme.在AppDelegate中实现application:openURL:options:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
NSString *urlStr = [url.absoluteString stringByRemovingPercentEncoding];
if ([urlStr hasPrefix:@"YDUDictionary://action="]) {
NSString *parameter = [urlStr stringByReplacingOccurrencesOfString:@"YDUDictionary://action=" withString:@""];
if ([parameter isEqualToString:@"CameraTranslate"]) {
//Do Somthinhg
}
}
}
主App中控制Widget是否显示
在Widget编辑页面可以进行Widget排序很删减。
当添加Widget以后,主工程还可以控制Widget是否显示。
//为什么要引入NotificationCenter呢?可以思考下 |
刷新机制
Widget有自己进程,有特殊的生命周期和内存限制。通过测试得出
Widget离开屏幕2s以上,就会被销毁回收掉。每次离开前系统会做快照处理。下次进来先加载快照。
离开超过2s以上,下次进入就会调用ViewDidLoad,然后是viewWillAppear
离开不超过2s 下次进入会调用viewWillAppear
所以为了交互体验,最好是记录用户上次的使用状态,下次加载时候进行还原操作。
当内存不足时候,系统会优先kill掉Widget。所以要注意内存问题,不要进行需要大量内存的操作。
网络请求如果需要频繁刷新。可以在viewWillAppear方法中启用一个Timer,在Timer中请求接口数据。在viewWillDisAppear中取消定时器。
如何在Widget中使用键盘
Apple官方文档说Widget是不支持键盘输入的。如果在TodayViewController中新建一个输入框。点击是没有反应的。但是我们可以用另外一种办法绕过去。效果如下图。
做法就是做一个假的输入框,让用户点击。点击后present一个ViewController,在这个Controller新建UITextView或者UITextField就可以获取焦点,出现键盘啦