一、简介

iOS应用程序扩展是苹果在iOS8推出的一个新特性,可以将自定义的功能和内容扩展到应用程序之外,在之后又经过不断地优化和更新,已经成为开发中不可或缺的功能之一。扩展也是一个Target项目,它运行在主机应用程序上,可以与主机应用程序实现资源共享,和宿主应用程序的Target项目是彼此独立的。系统提供的扩展有很多,Toady扩展就是其中之一,也被成为应用程序插件,它的作用是将今日发生的简单消息展示在系统的插件界面上。Toady扩展模板名称为Today Extension。图1是创建Today扩展,图2是扩展显示在插件界面上(可以通过点击Edit来添加或者移除扩展)。

二、创建

按照上图1的方式创建一个Today Extension的Target后,系统会默认帮我们生成一个TodayViewController控制器类、MainInterface.storyBoard故事板、plist序列化文件,文件结构图如下:

上图中红色圈内和箭头指向的配置就是系统通过MainInterface.storyBoard帮我们实现了一个基本的Toady插件UI布局,运行后可以直接显示在插件界面上。可是,有的时候开发者并不想使用系统的故事板来构建UI,系统支持自定义的,我们只需要修改plist配置即可。具体的配置是这样的:

[1] 将NSExtensionMainStoryboard字段删除;

[2] 添加NSExtensionPrincipalClass字段,修改value为控制器的类名。

[3] 在TodayViewController中的ViewDidLoad中设置preferredContentSize属性大小,用来调整widget界面UI的尺寸。

配置如下图所示:

//设置尺寸
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, );

三、分析

TodayViewController类比较简单,就是一个VC类,它实现了系统提供的一个扩展协议<NCWidgetProviding>,可以在协议方法中实现对扩展的更新和状态监控。

协议如下,都是可选的,开发者根据需要进行重写。

//协议
@protocol NCWidgetProviding <NSObject> @optional //当数据更新时调用的方法,系统会定期更新扩展
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult result))completionHandler; //监听显示模式(宽松型、紧奏型)和尺寸的改变,其中宽松和紧凑表示的是展开和折叠状态, iOS10开始才能使用
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize __API_AVAILABLE(ios(10.0)); //设置扩展UI边距,注意:使用StoryBoard时,若要所见即所得,则这个方法中需要返回UIEdgeInsetsZero; (iOS10 and later 不会再被调用,弃用了)
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets __API_DEPRECATED("This method will not be called on widgets linked against iOS versions 10.0 and later.", ios(8.0, 10.0)); @end
//扩展,都是iOS10开始才能使用
@interface NSExtensionContext (NCWidgetAdditions) //设置widget折叠或展开状态
@property (nonatomic, assign) NCWidgetDisplayMode widgetLargestAvailableDisplayMode __API_AVAILABLE(ios(10.0)); //只读,widget状态
@property (nonatomic, assign, readonly) NCWidgetDisplayMode widgetActiveDisplayMode __API_AVAILABLE(ios(10.0)); //获取widget不同状态的尺寸
- (CGSize)widgetMaximumSizeForDisplayMode:(NCWidgetDisplayMode)displayMode __API_AVAILABLE(ios(10.0)); @end

四、交互

Today扩展是寄宿于主机应用程序上的, TodayViewController又是一个UIViewController类,系统支持Today扩展对UIViewController进行切换。也就是说,苹果在考虑提供给开发者在对UIViewController中添加各种展示控件这种便利的同时,也相应的提供给开发者通过Today扩展的widget从主机应用程序激活并打开宿主应用程序的机会。不过这个操作必须通过设置并调起scheme来实现。步骤如下:

[1] 配置宿主应用程序的scheme;

[2] 使用扩展的openURL打开宿主应用程序。

交互如下:

//扩展通过scheme打开主宿主应用程序
[self.extensionContext openURL:[NSURL URLWithString:@"MainApp://"] completionHandler:nil];

五、数据

既然Today扩展能与宿主应用程序进行交互,那么肯定就存在数据通信的问题了。扩展与宿主目录应用程序位于不同的目录结构中,默认情况下,扩展与宿主应用程序的数据并不共享,代码也不能复用。例如在宿主目录应用程序中可能有网络请求、数据持久化存储等结构框架,在扩展中不可以直接使用,扩展需要提供自己的网络请求框架、数据持久化框架等。这些问题苹果都提供了解决方法,可以通过创建静态库的方式实现代码共享,通过APP Group和Scheme跳转实现数据共享。这里主要讲一下数据共享。注意:扩展和宿主应用程序的素材文件也是互相独立的,必须将扩展中的素材添加到扩展Target。

方式一:通过配置scheme跳转来实现数据共享。可以将传递的数据配置到URL中,然后宿主应用程序通过AppDeleagte的代理方法application:openURL:options:获取数据,不过这个数据传递只能是单方向的。

 //打开主应用程序
-(void)openMainApp { //共享数据
NSString *schemeFormat = @"MainApp://action=openCarema?name=xiayuanquan";
[self.extensionContext openURL:[NSURL URLWithString:schemeFormat] completionHandler:nil];
}
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {

    //从URL获取共享数据,截取数据
NSLog(@"---------url = %@---------",url); return YES;
}

方式二:给扩展的Target和宿主应用程序的Target项目都开启APP Group,两者配置相同的appgroupIndentifier标识,分别生成后缀名为.entitlements文件。然后对于小数据推荐使用偏好进行双向传递共享数据,如图所示。

//共享数据
//使用偏好设置
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
[defalut setObject:@"xiayuanquan" forKey:@"name"];
//从偏好设置获取共享数据
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
NSString *name1 = [defalut objectForKey:@"name"];
NSLog(@"1------------name1=%@",name1);

方式三:配置跟方式二一样,不过双向传递共享数据使用文件目录来实现。

//共享数据
//方式二:使用共享目录
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:@"xiayuanquan" requiringSecureCoding:NO error:nil];
[data writeToURL:filePath atomically:YES];
//从共享目录获取共享数据
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSData dataWithContentsOfURL:filePath];
NSLog(@"2------------data=%@",data);

六、适配

从iOS10开始,苹果提供了NCWidgetDisplayMode展示模式,通过设置该模式来支持对widget进行折叠和展开。在这里,preferredContentSize就用到了。这个是用来设置widget的尺寸的。苹果对widget的尺寸有自己的标准,width为maxSize.width,height取值范围[110, maxSize.height]。这个maxSize可以在扩展协议<NCWidgetProviding>的协议方法也即widgetActiveDisplayModeDidChange:withMaximumSize中获取:,可以发现每一种机型maxSize不一样。

// 6s模拟器下:
// NCWidgetDisplayModeCompact模式下:{359.000000, 110.000000}
// NCWidgetDisplayModeExpanded模式下:{359.000000, 528.000000} // 8 plus模拟器下:
// NCWidgetDisplayModeCompact模式下:{304.000000, 110.000000}
// NCWidgetDisplayModeExpanded模式下:{304.000000, 616.000000}

折叠状态:widget的高为110,此时设置preferredContentSize无效;

展开状态:widget的高为开发者设置的preferredContentSize.height,但是如果preferredContentSize.height>maxSize.height,此时取值为maxSize.height。

适配iOS10,默认支持展开,设置如下:

//设置widget默认为可以展开,此时处于折叠状态
#ifdef __IPHONE_10_0 //适配iOS10
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
#endif

七、范例

【去掉MainInterface.storyBoard,采用纯代码实现】

1、宿主应用程序AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch. //存储共享数据
//方式二:使用偏好设置
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
[defalut setObject:@"xiayuanquan" forKey:@"name"]; //存储共享数据
//方式三:使用共享目录
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:@"xiayuanquan" requiringSecureCoding:NO error:nil];
[data writeToURL:filePath atomically:YES]; return YES;
} -(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { //方式一:从URL获取共享数据,例如参数
NSLog(@"---------url = %@---------",url); return YES; }

2、Widget扩展TodayViewController

//
// TodayViewController.m
// TodayExtension
// Created by 夏远全 on 2019/11/19.
// #import "TodayViewController.h"
#import <NotificationCenter/NotificationCenter.h> @interface TodayViewController () <NCWidgetProviding> @end @implementation TodayViewController - (void)viewDidLoad {
[super viewDidLoad];
[self config];
[self createUI];
[self fecthData];
} //配置
-(void)config { self.view.backgroundColor = [UIColor lightGrayColor]; //widget背景色为灰色
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, ); //widget尺寸大小, 宽度实际取maxSize,width,高度[110, maxSize.height] //设置widget默认为可以展开,此时处于折叠状态
#ifdef __IPHONE_10_0 //适配iOS10
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
#endif } //创建UI
-(void)createUI { CGFloat width = self.view.frame.size.width;
CGFloat btnWidth = ;
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake((width-btnWidth)/, , btnWidth, )];
button.backgroundColor = [UIColor greenColor];
[button setTitle:@"OpenAPP" forState:UIControlStateNormal];
[button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(openMainApp) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
} //打开主应用程序
-(void)openMainApp { //传递共享数据
//方式一:参数传递
NSString *schemeFormat = @"MainApp://action=openCarema?name=xiayuanquan";
[self.extensionContext openURL:[NSURL URLWithString:schemeFormat] completionHandler:nil]; } //获取共享数据
-(void)fecthData { //方式二:从偏好设置获取共享数据
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
NSString *name1 = [defalut objectForKey:@"name"];
NSLog(@"1------------name1=%@",name1); //方式三:从共享目录获取共享数据
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSData dataWithContentsOfURL:filePath];
NSLog(@"2------------data=%@",data);
} //当数据更新时调用的方法,系统会定期更新扩展
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { //获取共享的数据,根据判断回调对应的block
//NCUpdateResultNewData,
//NCUpdateResultNoData,
//NCUpdateResultFailed completionHandler(NCUpdateResultNoData);
} //监听显示模式(宽松型、紧奏型)和尺寸的改变
//NCWidgetDisplayModeCompact : 折叠
//NCWidgetDisplayModeExpanded : 展开
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize { //maxSize:
//虽说是最大的Size,但苹果还是把Widget的高度范围限制在了[110 ~ maxSize]之间
//如果设置高度小于110,那么default = 110;
//如果设置高度大于开发者设置的preferredContentSize.Heiget,那么default = maxSize;
//折叠状态下,苹果将高度固定为110,这个时候设置preferredContentSize属性无效。
NSLog(@"width = %lf-------height = %lf",maxSize.width,maxSize.height); //可以更改状态
if (activeDisplayMode == NCWidgetDisplayModeExpanded) {
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, );
}
else{
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, );
}
} //设置扩展UI边距,注意:使用StoryBoard时,若要所见即所得,则这个方法中需要返回UIEdgeInsetsZero; (iOS10 and later 不会再被调用)
//- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets {
// return UIEdgeInsetsZero;
//} @end

3、打印和gif

-- ::31.074596+ TodayExtension[:] ------------name1=xiayuanquan
-- ::31.234435+ TodayExtension[:] ------------data={length = , bytes = 0x62706c69 d4010203 ... }
-- ::31.234970+ TodayExtension[:] maxSize.width = 359.000000-------maxSize.height = 110.000000 //折叠
-- ::38.117764+ TodayExtension[:] maxSize.width = 359.000000-------maxSize.height = 528.000000 //展开

iOS:应用程序扩展开发之Today扩展(Today Extesnsion)的更多相关文章

  1. Android开发之ExpandableListView扩展(BaseExpandableListAdapter的使用)(完整版)

    Android开发之ExpandableListView扩展(BaseExpandableListAdapter的使用)(完整版)

  2. 前端开发之VSCode扩展

    1.Chinese (Simplified) Language Pack for Visual Studio Code——中文语言包 2.Beautify——代码格式化工具 3.HTML Snippe ...

  3. iOS多线程开发之GCD(死锁篇)

    上篇和中篇讲解了什么是GCD,如何使用GCD,这篇文章将讲解使用GCD中将遇到的死锁问题.有兴趣的朋友可以回顾<iOS多线程开发之GCD(上篇)>和<iOS多线程开发之GCD(中篇) ...

  4. iOS开发之Socket通信实战--Request请求数据包编码模块

    实际上在iOS很多应用开发中,大部分用的网络通信都是http/https协议,除非有特殊的需求会用到Socket网络协议进行网络数 据传输,这时候在iOS客户端就需要很好的第三方CocoaAsyncS ...

  5. iOS开发之UISearchBar初探

    iOS开发之UISearchBar初探 UISearchBar也是iOS开发常用控件之一,点进去看看里面的属性barStyle.text.placeholder等等.但是这些属性显然不足矣满足我们的开 ...

  6. iOS开发之UIImage等比缩放

    iOS开发之UIImage等比缩放 评论功能真不错 评论开通后,果然有很多人吐槽.谢谢大家的支持和关爱,如果有做的不到的地方,还请海涵.毕竟我一个人的力量是有限的,我会尽自己最大的努力大家准备一些干货 ...

  7. iOS开发之 Xcode6 添加xib文件,去掉storyboard的hello world应用

    iOS开发之  Xcode6.1创建仅xib文件,无storyboard的hello world应用 由于Xcode6之后,默认创建storyboard而非xib文件,而作为初学,了解xib的加载原理 ...

  8. ios开发之OC基础-类和对象

    本系列的文章主要来自于个人在学习前锋教育-欧阳坚老师的iOS开发教程之OC语言教学视频所做的笔记,边看视频,边记录课程知识点.建议大家先过一遍视频,在看视频的过程中记录知识点关键字,把把握重点,然后再 ...

  9. ios开发之OC基础-oc小程序

    本系列的文章主要来自于个人在学习前锋教育-欧阳坚老师的iOS开发教程之OC语言教学视频所做的笔记,边看视频,边记录课程知识点.建议大家先过一遍视频,在看视频的过程中记录知识点关键字,把把握重点,然后再 ...

随机推荐

  1. cobalt strike笔记-常用beacon扫盲

    最近还是重新补一下cs的东西 0x01 Beacon命令 Beacon Commands =============== Command Description ------- ----------- ...

  2. 关于vue使用的一些小经验

    这一年来说,vue的势头很猛,用户量“噌”“噌”“噌”的涨 为了不掉队不落伍.在后台大哥的胁迫下,不得不选择用了它 刚开始很难接受vue的写法,在编辑器里很容易报错,基本上每行都会出现红色的波浪线 这 ...

  3. C# 8.0 的默认接口方法

    例子 直接看例子 有这样一个接口: 然后有三个它的实现类: 然后在main方法里面调用: 截至目前,程序都可以成功的编译和运行. IPerson接口变更 突然,我想对所有的人类添加一个新的特性,例如, ...

  4. 仿写vue UI 组件总结 (自己练习,仿照现有的UI组件)

    UI组件 Vue开发插件流程 本来是昨天要写总结的,感觉自己写不好,就放弃了.今天看到了iview和element有一些摩擦,是关于代码借鉴的问题(哈哈),不做评价.谁下生会写组件,我仿(chao)写 ...

  5. Java基础(三十二)JDBC(2)连接数据库

    一.连接数据库的过程 连接数据库的过程:加载数据库驱动程序,不过只需在第一次访问数据库时加载一次,然后在每次访问数据库时创建一个Connection实例,然后执行操作数据库的SQL语句,并返回执行结果 ...

  6. Java多线程编程(四)Lock的使用

    一.使用ReentrantLock类 在Java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,但ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大, ...

  7. 加上cdn后字体跨域

    @font-face是CSS3中的一个特性,可以把自己定义的Web字体嵌入到网页中,随着@font-face,越来越多的网页采用字体图标作为网页中的小图形. 比如Bootstrap就采用了Glyphi ...

  8. vw vh 的概念

    视口单位(Viewport units) 什么是视口? 在桌面端,视口指的是在桌面端,指的是浏览器的可视区域:而在移动端,它涉及3个视口:Layout Viewport(布局视口),Visual Vi ...

  9. Unity3-各个内置面板,对象说明

    *在Inspector面板中,是表示每个物体(诸如摄像机,圆柱,正方体)的组件. 组件包含: 1.Transform,在第一节当中,可以用于变换物体的位置.每个物体对象都有. 2.cube,网格,对于 ...

  10. CSPS模拟 46

    勿忘国耻. 由于重新评测我看到了不是很真实的一幕 紧接着是更不真实的一幕 就在虚假形象快要建立完成的时候 它由于来自东方的神秘力量倒塌了 被两个学校的大佬爆踩了(捂脸 T1 无脑背包? 考试时想1h想 ...