原文
讲述之前首先看下demo效果图:
基本的展开收起、本App本体交互
然后再展示几个效果不错的 Widget app
毒物 && Keep
ESPN
PCalc
Musixmatch
Fantastical 2
Carrot Weather
demo 地址在此!欢迎star
一、Widget总览
二、Widget代码实现
因为 Widget 属于单独的进程,因此需要再新建一个target:File -> New ->target
初次构建 UI 时,运行 Widget 后会发现,Widget左侧距离屏幕左侧始终有一段距离,导致效果不佳,可以通过下面的代理方法消除间距
// 取消widget默认的inset,让应用靠左
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets {
return UIEdgeInsetsZero;
}
Widget 的收起、展开 则是通过这个代理方法:
/**
activeDisplayMode有以下两种
NCWidgetDisplayModeCompact, // 收起模式
NCWidgetDisplayModeExpanded, // 展开模式
*/
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
if(activeDisplayMode == NCWidgetDisplayModeCompact) {
// 尺寸只设置高度即可,因为宽度是固定的,设置了也不会有效果
self.preferredContentSize = CGSizeMake(0, 110);
} else {
self.preferredContentSize = CGSizeMake(0, 310);
}
}
在设置 UI 的过程中,若想使用本体 Target 中的类:
在对应类的 Target Membership 勾选 Widget 即可
如果想使用Pod 管理的第三方库,那么只需要以下三步就可以愉快地玩耍了(比如我想使用 Masonry 布局)
1、 在podfile文件中
2、 按照如图所示配置configurations
3、 最后分别配置两个 Target 的 link Binanry
当然有些第三方包含 source 文件的可能还需要别的操作,最简单粗暴的方式就是-->拖进去!
使用图片也是必不可少,然而 imageNamed: 和 imageWithContentsOfFile: 两种方式加载都不行,即使设置了文件的 target 为 Widget Extension,后来在其target 内部建立一个 .xcassets 文件即可加载图片
然而在 Widget Extension 里面新建类又出现了如下报错
造成这个的原因是新建的时候默认是 C header,而且没有指向对应的target,按照下图所示修改一下type,选一下target,再次编译就木有问题了
如果需要网络请求,记住在 Extension 的plist文件中添加App Transport Security Settings 属性。
在开发过程中,那么怎么一直有个“Hello World”显示,最后看了一下原来是 Storyboard 加载,去 Storyboard 文件删除对应 label 即可。
如果你的项目中要求纯代码
三、与 App 本体交互
与本体 app 进行交互之前,要明白的一个概念是:Widget 与 app 本身 是两个target,appId 也是独立的,因此 Widget 与本体 app 是通过 app group 进行交互
1、设置群组关系
在 本体 App 的 target > Capabilities添加 container 标识符
这个写好之后,再去扩展的target做相同的操作,标识符一定要一样!!
切换 target 的方法在这里
报错信息:[_NCWidgetExtensionContext openURL:completionHandler:]_block_invoke failed: Error Domain=NSOSStatusErrorDomain Code=-50 "(null) 如果报这个错说明 urlScheme有问题,没有标准对应,比如下划线识别等
2、设置 scheme 进行交互
设置 app 的 scheme 标识符
在plist 文件内添加以下键值对
然后!就可以在 Widget 对应的点击事件里面
// 扫一扫按钮的点击事件
- (void)scanBtnTapped:(UIButton *)sender {
[self.extensionContext openURL:[NSURL URLWithString:@"wpfWidgetTest://action=richScan"] completionHandler:^(BOOL success) {
NSLog(@"scanBtnTapped open url result:%d",success);
}];
}
在 app 本体的 AppDelegate 方法里面
// 处理 Widget 相关事件
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
NSString* prefix = @"wpfWidgetTest://action=";
NSString *urlString = [url absoluteString];
if ([urlString rangeOfString:prefix].location != NSNotFound) {
NSString *action = [urlString substringFromIndex:prefix.length];
if ([action isEqualToString:@"richScan"]) {
// 进入到扫一扫页面
[self.rootVC transferToRichScanVC];
} else if ([action isEqualToString:@"web"]) {
// 进入到 web 活动页
[self.rootVC transferToWebVCWithUrlString:@"webTest"];
}
}
return YES;
}
数据共享:widget项目必然经常要和主项目共享数据,可以通过NSUserDefault,注意和平时用有些不同,创建UserDefault的时候,要指定groupid。上代码:
// widget项目里取数据
+ (NSString*)widgetStringForKey:(NSString*)defaultName {
NSUserDefaults*shared = [[NSUserDefaultsalloc] initWithSuiteName:@"group.com.widgetTest"];
return[shared stringForKey:defaultName];
}
// 主项目里存数据
+ (void)widgetSetObject:(id)value forKey:(NSString*)defaultName {
NSUserDefaults*shared = [[NSUserDefaultsalloc] initWithSuiteName:@"group.com.widgetTest"];
[shared setObject:value forKey:defaultName];
[shared synchronize];
}
#warning 涉及到大量数据交互也可以使用 NSFileManager 进行数据共享
在demo中,实现了从Widget入口 点击未读消息后,下次不再展示该未读消息项
四、关于刷新时机
Widget 自身的更新机制,是进入到 Widget 页面后(iOS 10 左滑,之前是下拉),先执行 viewDidLoad 方法,然后是 viewWillAppear 方法,但是经测验,Widget 页面在屏幕消失超过两秒后(手机没有停留在 Widget 页面 或者 停留在别的app 的Widget页面,自己的没显示)
由于以上特性,更新代码最好写在 viewWillAppear 方法里面,对于更新时效性特别强的,比如天气类 app,这种最好就是 在该方法里面添加一个 NSTimer 定时进行刷新,在 viewWillDisAppear 方法中 进行 取消NSTimer invalidate定时更新即可
知乎、得到 app的 Widget,只要走 viewDidLoad 方法就会闪一下(如下图),因为每次Widget加载请求的数据后会进行替换造成的。这里可以做个缓存优化,判断如果请求来的数据和当前数据内容一致,那么就不进行刷新列表操作
不信你看
五、关于 iOS 8 适配
iOS8、9是老式的下拉刷新,并没有折叠和展开功能,默认的Widget高度为self.preferredContentSize设置的高度
iOS8 默认的背景是黑色磨砂效果,iOS10默认的背景色是白色磨砂效果。因此在控件颜色上做下适配
iOS8效果图
iOS8下所有组件默认右移30pt
六、其他注意点
1.当程序内存不足时,苹果优先会杀死扩展,因此需要注意内存的管理。
2.在配置team是账号需要一致(免费账号不行,需要付费的账号),上传包的时候一定注意选择 Product -> Archive -> 选择 distribution 模式!
3.3D touch 对应的也有Widget!?答案是 YES!,只要设置了3D touch,Widget的第一栏就会自动显示
再次附上 demo Github 地址,欢迎star