美文网首页iOS学习笔记EdisoniOS
从0到1思考与实现iOS-Widget

从0到1思考与实现iOS-Widget

作者: si1ence | 来源:发表于2017-03-22 11:50 被阅读2647次

讲述之前首先看下demo效果图:

基本的展开收起、本App本体交互

然后再展示几个效果不错的 Widget app


毒物 && Keep ESPN PCalc Musixmatch Fantastical 2 Carrot Weather

demo 地址在此!欢迎star

比心

一、Widget总览

  • Widget 是 iOS8 推出第一版,在iOS 10 进行大幅度的优化
  • Widget可以让用户更快地访问到其感兴趣的内容,官方的说法是用来呈现功能比较简单的,交互性不强的东西,在不打扰或者中断用户使用当前应用的前提下完成自己的功能点.对于这个说法,国内的开发者表示呵呵,因为几乎所有的 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 即可
  • 如果你的项目中要求纯代码
    • 删除 Storyboard 文件和plist 对应键值对
    • 添加 NSExtensionPrincipalClass 字段并设置为 TodayViewController



三、与 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加载请求的数据后会进行替换造成的。这里可以做个缓存优化,判断如果请求来的数据和当前数据内容一致,那么就不进行刷新列表操作
    不信你看

五、关于 iOS8 适配

  • iOS8、9是老式的下拉刷新,并没有折叠和展开功能,默认的Widget高度为self.preferredContentSize设置的高度
  • iOS8 默认的背景是黑色磨砂效果,iOS10默认的背景色是白色磨砂效果。因此在控件颜色上做下适配
iOS8效果图
  • iOS8下所有组件默认右移30pt

六、其他注意点

  1. 当程序内存不足时,苹果优先会杀死扩展,因此需要注意内存的管理。

  2. 在配置team是账号需要一致(免费账号不行,需要付费的账号),上传包的时候一定注意选择 Product -> Archive -> ** 选择 distribution 模式!**

  3. 3D touch 对应的也有Widget!?答案是 YES!,只要设置了3D touch,Widget的第一栏就会自动显示。但是如果有多个widget的话,还需要在 info.plist 指定相应的main target!

Extension 证书配置指南
官网说明
一直很心仪的app --> Things 关于widget的介绍
几个精致的 Widget app
在模拟器上进行3D touch 测试


再次附上 demo Github 地址,欢迎star

相关文章

  • iOS开发集锦之 2017.03.23(设计模式)

    1.暂未更新 2. 从0到1思考与实现iOS-Widget 作者: si1ence源码: WidgetTest描述...

  • 从0到1思考与实现iOS-Widget

    讲述之前首先看下demo效果图: 然后再展示几个效果不错的 Widget app demo 地址在此!欢迎star...

  • 从0到1的思考

    对于一个没有太多商业素养而且对外文译书时常水土不服的人来说(上一本《必然》读了两遍才大致看明白),读《从0到...

  • 从0到1与从1到N

    “从0到1”这个词很多人都有所耳闻,这完全要归功于彼得·蒂尔那本书《从0到1:开启商业与未来的秘密》。彼得·蒂尔在...

  • 从0到1与从1到N

    这个就像文化和文明,当有了城市才算有文明,文化的起源大家都可以不停的向前追溯,除了吹牛,有啥实际意义呢。 每个大的...

  • 从0到1与从1到N

    一、背默: 今天的背默速度居然翻倍了,我仔细回忆了下,首先是背默的时间,我利用记忆规律中前摄抑制和后摄抑制的...

  • 从0到0,从0到1。

    昨天和一客户交流,听到这么一句话,我现在的阶段勉强算0到0的阶段,到那个1的阶段还没有看到,或者说并不知道那个1在...

  • 从0到1

    《从0到1》 最特立独行的事情不是去反对大众意见,而是有自己的独立思考。 书名:《从0到1:开启商业与未来的秘密》...

  • 《从0到1》读书笔记

    一、从0到1的思考 从0到1,zero to one。 从0到1,是从无到有,是垂直进步,是创新和质变,需要的是发...

  • 《从0到1》读书小札与思考

    书名:《从0到1》作者:彼得·蒂尔读书小札日期:2021年6月19号 1、这一本书要从“PayPal黑帮”说起··...

网友评论

  • 新地球说着一口陌生腔调:请问这个方法widgetActiveDisplayModeDidChange 是写到哪里去
    si1ence:@新地球说着一口陌生腔调 可以看下demo
  • 28bb64fffadd:- (void)scanBtnTapped:(UIButton *)sender {
    NSLog(@"计算器----today");
    [self.extensionContext openURL:[NSURL URLWithString:@"smartruler://action=calculator"] completionHandler:^(BOOL success) {
    NSLog(@"计算器BtnTapped open url result:%d",success);
    }];
    }
    想问下为什么调试的时候,在此处打的断点已经nslog都不显示,另外的话,想问下此种的block在什么回那到返回值呢
  • 开发者头条_程序员必装的App:感谢分享!已推荐到《开发者头条》:https://toutiao.io/posts/nzzoeg 欢迎点赞支持!
    欢迎订阅《iOS技术分享》https://toutiao.io/subject/204963
  • 逆光少年:第一次尝试,使用本体 Target 中的类,点勾的地方,我点了然后切回这个controller点的勾又没了是什么情况,跟着你的操作就可以吗?
  • SunAndSun:实际上,苹果的widget本身做得非常不好。比如不同机型的最大高度问题,下拉通知中心和today页面最大高度不等的问题,和那个所谓110的折叠固定高度,都是造成一系列不可适配问题。你可以尝试改一下系统字体大小,再回头研究一下widget的适配看看有什么好方式
  • daa47d66949b:mark下~~~
  • 风不会停歇:mov 你怎么转的GIF
    si1ence:@风不会停歇 直接录得gif
  • Samson_Xu:你好,我在配置证书的时候点了Automatically manager signing,但是只要一创建APP Groups就会出现报错,后面两个Add···to AppID的选项都报警,你知道是什么原因吗?Demo也是这样,真机跑不起来
    Samson_Xu:@si1ence 对的,因为是配置证书出问题了,不过刚才我把group重新命名,换了几次名字之后可以了。
    si1ence:@Samson_Xu 模拟器可以跑起来么?
  • c9c8fae09afd:您好,Widget-today.entitlements的作用是什么?谢谢
    si1ence:@天地一沙鸥_5b87 每一个子 target 都有一个这个文件,标识 group 信息的
    c9c8fae09afd:@si1ence 作用是什么
    si1ence:@天地一沙鸥_5b87 存放 group 相关信息的 plist 文件
  • cef84bbc10d6:3D Touch 显示widget,如果有多个widget的话,还需要在 info.plist 指定相应的main target
    si1ence:@码农六叔 好的,谢谢提醒:clap:
  • SMFly:补充下:添加图片的时候不是不可以用两个加载方法,添加图片的时候 addToTargets时记得补充选择today的Targets就可以了;这个是和多target开发是一致的。
    SMFly:@si1ence 亲测可用
    si1ence:@SMFly 稍后我再试一下,当时我勾选了target也不行
    SMFly:对于已有的图片(还没添加至today的target),可以设置图片的Target Membership,勾选today-Target
  • 春泥Fu:看着感觉高大上啊~
  • Tr2e:评论才是真爱
  • d94098065219:看大神的文章很久了。每篇都受益匪浅。
  • CoderDancer:太牛了

本文标题:从0到1思考与实现iOS-Widget

本文链接:https://www.haomeiwen.com/subject/qqfknttx.html