iOS Widget的简单实现

自iOS8之后,苹果支持了扩展(Extension)的开发,开发者可以通过系统提供给我们的扩展接入点 (Extension point) 来为系统特定的服务提供某些附加的功能。今年iOS10的推出,让Widget扩展应用渐渐的火了起来,地位得到重大的提升,从这也可以看出苹果对他的重视,今天我们就来一起学习下Widget,来实现一个简单的扩展程序。

iOS Widget扩展程序

程序效果

程序效果图

创建Widget程序

  • 创建工程,在工程中添加扩展程序
创建Widget程序
  • 创建成功后的目录
创建成功

顺便说一句,扩展程序虽然是程序的扩展,但是这两个应用其实是“独立”的。准确的来说,它们是两个独立的进程,默认情况下互相不应该知道对方的存在。扩展需要对宿主 app (host app,即调用该扩展的 app) 的请求做出响应,当然,通过进行配置和一些手段,我们可以在扩展中访问和共享一些容器 app 的资源,这个我们稍后再说。

Widget布局方式

  • 使用Interface Builder
    工程默认的方式就是使用Interface Builder,如果实现简单的布局的话可以考虑这种方式。
  • 使用代码进行布局
    当涉及到比较复杂的UI布局的时候,可以考虑使用这种布局方式,按大家平时的习惯来。这里需要注意一下,如果需要使用代码布局的话需要修改一下plist文件。
    首先将原有NSExtensionMainStoryboard字段删除,添加字段NSExtensionPrincipalClass,value是你所写的controller的名称,一般默认的都是TodayViewController
修改plist文件

实现相应的方法

1. 设置Widget的size
iOS10之后,Widget支持展开及折叠两种展现方式,通过设置widgetLargestAvailableDisplayMode属性可以让Widget程序实现展开布局。如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    
    if (isIOS10)
    {
        self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
    }
    
    self.preferredContentSize = CGSizeMake(kWidgetWidth, 110);
}

2. 重写切换展开及折叠布局时的方法(iOS10之后)

- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize
{
    NSLog(@"maxWidth %f maxHeight %f",maxSize.width,maxSize.height);
    
    if (activeDisplayMode == NCWidgetDisplayModeCompact)
    {
        self.preferredContentSize = CGSizeMake(maxSize.width, 110);
    }
    else
    {
        self.preferredContentSize = CGSizeMake(maxSize.width, 200);
    }
}

3. iOS10之前,视图原点默认存在一个间距,可以实现以下方法来调整视图间距
注:该方法在iOS10之后被遗弃,iOS10默认不存在间距。

- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
    return UIEdgeInsetsMake(0, 10, 0, 10);
}

应用唤醒

本来想叫应用间跳转的,想想还是这个名字比较高大上些😏
如下,配置url scheme,这个定义的时候尽量不要和其他用用冲突,笔者定义的为WidgetDemo。这样,通过访问WidgetDemo://就可以实现应用唤醒了。代码如下:

配置url scheme
- (void)redButtonPressed:(UIButton *)button
{
    NSLog(@"%s",__func__);
    
    NSURL *url = [NSURL URLWithString:@"WidgetDemo://red"];
    
    [self.extensionContext openURL:url completionHandler:^(BOOL success) {
        
        NSLog(@"isSuccessed %d",success);
    }];
}

相应的,在AppDelegate中实现以下方法,这里可以处理传过来的action,对于传过来不同的值可以进行不同的操作,这里我们打印了请求url的内容。

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    if ([[url absoluteString] hasPrefix:@"WidgetDemo"])
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:[NSString stringWithFormat:@"你点击了%@按钮",[url host]] delegate:nil cancelButtonTitle:@"好的👌" otherButtonTitles:nil, nil];
        [alert show];
    }
    return  YES;
}
  • 简易的应用快速启动器
    既然说到了应用唤醒,这里再稍稍拓展以下,想必大家都有用过类似launcher这种的应用快速启动器。其实就是运用了应用间跳转的原理,每款应用都有自定义的url scheme,我们只要知道他们的url scheme就可以跳转至改款应用,例如进行微信的跳转:
- (void)wechatLoginButtonPressed
{
    NSLog(@"%s",__func__);
    
    NSURL *url = [NSURL URLWithString:@"wechat://"];
    
    [self.extensionContext openURL:url completionHandler:^(BOOL success) {
        
        NSLog(@"isSuccessed %d",success);
    }];
}

以下是我们比较常用的软件的url scheme,有兴趣的同学们可以试一试:
QQ mqq:// 微信 weixin:// 淘宝taobao:// 微博 sinaweibo:// 支付宝alipay://

数据共享

扩展程序一般都不是脱离宿主程序单独运行的,难免需要和宿主程序进行数据交互。而相对于一般的APP,数据可以用单例,NSUserDefault等等。但由于拓展与宿主应用是两个完全独立的App,并且iOS应用基于沙盒的形式限制,所以一般的共享数据方法都是实现不了数据共享,这里就需要使用App Groups。

  • 在宿主程序和扩展程序中分别设置打开App Group,设置一个group的名称,这里要保证宿主APP和扩展APP的groupName要是相同的。
设置App Group

两种数据存储方式

  • 使用NSUserDefault
    这里不能使用[NSUserDefaults standardUserDefaults];方法来初始化NSUserDefault对象,正像之前所说,由于沙盒机制,拓展应用是不允许访问宿主应用的沙盒路径的,因此上述用法是不对的,需要搭配app group完成实例化UserDefaults。正确的使用方式如下:
    写入数据
    NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.japho.widgetDemo"];
    [userDefaults setObject:self.textField.text forKey:@"widget"];
    [userDefaults synchronize];

读取数据

    NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.japho.widgetDemo"];
    self.contentStr = [userDefaults objectForKey:@"widget"];
  • 通过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;
}

其他

补充:widget的上线也是需要单独申请APP ID的 需要配置证书和Provisioning Profiles文件
没有配置相关证书时:



配置证书及描述文件:(列举一些)




证书与描述文件配置好之后:

Demo

博主双手奉上demo,同学们如果喜欢就给我点个star吧~~ 😘

最后感谢一下文章的博主😏

Widget的简单应用并适配iOS10
iOS开发------Widget(Today Extension)插件化开发

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容