iOS开发之widget实现

前言

    iOS extension的出现,方便了用户查看应用的服务,比如用户可以在Today的widgets中查看应用的简略信息,然后点击进入相关的应用界面。
暂且不表网络上现有的widget文章,本篇文章主要说明本人具体实现widget的步骤,希望能够帮助到需要实现widget的同行朋友。


图1 Today的widget展示----以支付宝为例说明

文章将依次从以下几个问题着手,进行详细说明:
1、如何为现有的工程添加widget;
2、如何绘制UI;
3、如何调起app;
4、如何与host app共享数据。

添加Today Extension


图2 添加today的target

   如图,为现有的工程添加Today Extension,命名这里不赘述了,大家都懂的。


图3 添加today之后的工程目录

    这是添加Today Extension之后的工程目录。
    到这里,为现有的工程添加Today Extension算是完成了,运行程序就可以看到类似图1的简单的效果了,很简单哈。

绘制UI

    与网上发表文章的童鞋们一样,我也是个代码控(装B一下),习惯用纯代码来绘制Today的UI。


图4 删除默认创建的MainInterface并修改Info.plist

    这里,删除默认创建的MainInterface.storyboard,并按图4所示修改Info.plist文件。(当然,习惯使用storyboard的童鞋可以略过,直接操作storyboard即可)


图5 设置widget展示视图的大小

    首先,设置widget展示视图的大小。关于widget的背景色,以及具体展示的内容大家按需绘制,这里暂且不表。


图6 设置widget视图距离左侧边界距离为0

    运行程序后,会发现一个问题:绘制的内容与左侧边界有一定距离(约30px)。如何解决这个问题呢,如图6所示,TodayViewController遵守的NCWidgetProviding协议给出了解决方案。

调起app

    因为extension和containing app是两个完全独立的进程,所以它们之间不能直接通信(不能像应用内部点击按钮,跳转到指定页面)。为了实现widget调起app,这里通过openURL的方式来启动containing app。
- (void)openURLContainingAPP{ //通过extensionContext借助host app调起app [self.extensionContext openURL:[NSURL URLWithString:@"appextension://xxx"] completionHandler:^(BOOL success) { NSLog(@"open url result:%d",success); }];}

数据共享


图7 添加App Groups

    通过App Groups提供的同一group内app共同读写区域,可以用NSUserDefaults和NSFileManager两种方式实现extension和containing app之间的数据共享。

1 通过NSUserDefaults共享数据

保存数据
- (void)saveDataByNSUserDefaults{ NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.xxx"]; [shared setObject:@"asdfasdf" forKey:@"widget"]; [shared synchronize];}
读取数据
- (NSString *)readDataFromNSUserDefaults{ NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.xxx"]; NSString *value = [shared valueForKey:@"widget"]; return value;}

2 通过NSFileManager共享数据

保存数据
- (BOOL)saveDataByNSFileManager{ NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.xxx"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/widget"]; NSString *value = @"asdfasdfasf"; BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err]; if (!result) { NSLog(@"%@",err); } else { NSLog(@"save value:%@ success.",value); } return result;}
读取数据
- (NSString *)readDataByNSFileManager{ NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.xxx"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/widget"]; NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err]; return value;}
    到这里,在Today中添加应用的widget就完成了,从Today中可以浏览预设的快捷服务,接着说iOS 10的适配问题。

一、前言

    前面一篇iOS开发之widget实现文章说到了如何在iOS 8.0-9.3上实现widget,可是iOS 10已经来袭,不了解一下iOS 10,把widget适配上去,说不过去呀!所以,本篇文章就接着说下iOS 10上面Today Widget的坑坑洼洼。

二、Today Widget新特性

    安装完iOS 10的beta版本,发现苹果越发重视widget了:快速浏览,及时从喜爱的应用中了解信息,如图1所示。
    现在,从锁屏页面,下拉通知栏的第一页,还有左滑主页面都可以进入widget。通过右上角的“展开”、“折叠”按钮,可以查看widget的全部内容和部分内容。在6s上面,点击应用icon的3D Touch界面中,也会有widget的折叠界面。


iOS10 Today Widget界面.png

三、适配问题

1、在widget中,展开、折叠具体是怎么实现的呢?

    在NSExtensionContext中,新添了widgetLargestAvailableDisplayMode属性,来确认当前widget是展开还是折叠状态。所以,先在viewWillAppear中设置widget的mode为展开。
代码段1
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;}
    然后,就是展开和折叠的处理了。在NCWidgetProviding协议中,新添了这么个方法widgetActiveDisplayModeDidChange,不赘述,直接用代码示例说明它的用途:
代码段2
// If implemented, called when the active display mode changes.// The widget may wish to change its preferredContentSize to better accommodate the new display mode.
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize { if (activeDisplayMode == NCWidgetDisplayModeCompact) { self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 110); } else { self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300); }}
    到这里,你可以在iOS 10上面看到带有展开、折叠功能的widget了,具体UI上面的微调,这里暂且不表,聪明的你肯定早就知道解决方案了。但是,不要着急,容我再把自己遇到的坑给家接着叨叨。

2、启动app后,第一次显示的是折叠,而不是展开呢?

    这个问题,归咎于preferredContentSize的设置,确认widget的mode之前,不要设置这个值。我的处理方式是:在widgetActiveDisplayModeDidChange中设置展开或折叠状态下widget的高度,就如上面的代码段2一样(当然,我的项目里比这个处理要复杂的多,这里化繁为简只为示例),iOS 10环境下在这里设置高度也就足够了。

3、为什么有时展开或折叠"失灵"了,没有对应的展开或折叠呢?

    这个问题的前提,肯定是你展开、折叠对应的widget高度不一样,只是看到了右上角按钮内容改变,但高度却没有变。
    这个问题的原因在于,点击展开、折叠按钮修改了widget的mode之后,却没有设置对应的高度:preferredContentSize。怎么办呢?再回到代码段2,mode改变后,设置对应状态下的高度即可。

4、如何用XCode 7.3打出能够适配iOS 10的widget呢?

    适配完iOS 10,会发现代码中总不能用XCode 8.0 beta打包代码吧,可是用XCode 7.3打包代码也编译不过啊,widgetLargestAvailableDisplayMode和NCWidgetDisplayMode都是iOS 10的产物。
    用kvc试试看?对,就是kvc。闲言不表,直接看代码吧:
代码段3 相对于 代码段1
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.extensionContext setValue:@"1" forKey:@"widgetLargestAvailableDisplayMode"];}

代码段4相对于 代码段2
- (void)widgetActiveDisplayModeDidChange:(NSInteger)activeDisplayMode withMaximumSize:(CGSize)maxSize { if (activeDisplayMode == 0) { self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 110); } else { self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300); }}

5、切记:UI的更新要在主线程操作哈!!!比如酱紫:

//通知主线程刷新dispatch_async(dispatch_get_main_queue(), ^{ //...........;});
好了,我在适配widget过程的问题就这些了,希望可以帮助正在开发widget遇到同样问题的朋友。



原文链接:http://www.jianshu.com/p/ca3e11d7686c


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值