需求
一切的结果皆是需求所产生的
笔者主要目的是想要作出下图效果(上半部分)
![](https://i-blog.csdnimg.cn/blog_migrate/eac309a6a608344949aba1ebfd5ed040.png)
首先我们知道这是一种Today Extension,上图是通过3DTouch触按弹出的,我们也可以在今日通知栏里添加看到
最终的效果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/c230be0516fa19458f961fc7fcd16457.gif)
着重点
1、创建Today Extension
2、实现扩展和宿主App之间共享数据
3、使用宿主App中的文件
4、扩展中打开宿主App
5、补充:读取xib文件、扩展中支持三方框架、参数传递、扩展Widget高度、上架注意事项
1、创建Today Extension
首先,我们选中项目文件,选择 xcode ->Editor ->Add Target,如下图,选中Today Extension项,然后点击Next,命名(本文中为MyTodayWidget),在弹出框中选择Activate,激活这个scheme
![](https://i-blog.csdnimg.cn/blog_migrate/365d76c27329087be661d34f8dd89db6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a0c400dc397e321585baef2a5b638302.png)
我们先运行项目,在运行应用扩展
![](https://i-blog.csdnimg.cn/blog_migrate/c7dceb19215e34c5572a6dd8b2a8e1b5.png)
上述就完成了Today Extension的创建
当然你可以将扩展中的plist中的displayname更换为宿主应用名称,在TodayViewController完成项目需要的UI
2、实现扩展和宿主App之间共享数据
在Today Extension开发中,避免不了要和宿主App之间共享数据,比如,笔者的项目中需要使用项目中的域名、三方平台请求头部、服务器数据地址等等;
扩展与宿主App之间共享数据有两种方式:
1.通过NSUserDefaults
2.通过一个扩展与App都可以访问的共享容器,来存放文件,数据(Core Data, Sqlite等都可以存放在这个共享的容器中)
首先,我们需要创建一个app group,如下图,选中项目的Target -> Capabilities -> App Groups,打开,如果你以前创建过group,会自动列出来。选择+号,填入group的名称(记下这个名称,因为这个是扩展和宿主之间共享数据的标志符)
![](https://i-blog.csdnimg.cn/blog_migrate/92b9817e44783db5dabdb804b066e93d.png)
创建完成之后,选择扩展的Target -> Capabilities -> App Groups,打开,选择我们刚才所创建的group
⚠️: 如果出现了错误,应该是名称不可用,换一个重试
也可以登录开发中选中《App Group》创建
![](https://i-blog.csdnimg.cn/blog_migrate/40a319e5253fc83c0530b33da8d542b1.png)
在扩展和宿主App打开group之后,项目中会多出两个文件,如下图
![](https://i-blog.csdnimg.cn/blog_migrate/46e6b1e41a54091158db0f5b40f9185b.png)
// 存储数据
[[[NSUserDefaults alloc] initWithSuiteName:@"group.com.LOLITA.appExtension"] setValue:myNote forKey:@"myShareData"];
// 取出数据 NSArray *myData = [[[NSUserDefaults alloc] initWithSuiteName:@"group.com.LOLITA.appExtension"] valueForKey:@"myShareData"];
这样我们可以在扩展和宿主App之间存取共享的数据了
补充:
如果需要存储更多的数据,可以通过文件或者数据库(Core Data, Sqlite等)。这个时候共享数据的方法就是要创建一个共享的文件夹
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier: @"group.com.LOLITA.appExtension"];
通过上面的方法,扩展和App就都可以访问这个共享的文件夹了,将数据库,文件等存储在这个文件夹中,也同样的达到数据共享的目的。
3、使用宿主App中的文件
在扩展中,总是避免不了想要使用宿主项目中的文件,例如cell样式,数据处理工具等等,重写一份当然是可以的,但不是我们想要的结果。
我们可以将需要用的文件也供用给扩展,步骤如下
打开.m文件,选中下图按钮
![](https://i-blog.csdnimg.cn/blog_migrate/280d6d952adec5b4937edd1b0ca0f60a.png)
这样我们就可以在扩展中使用该文件了
⚠️:在选择的文件中,如果包含了其他文件,一样是需要添加到扩展中的
4、扩展中打开宿主App
既然扩展作为了宿主App消息的展示栏,肯定应用的入口了,那么我们怎么让扩展和App之间进行消息传递呢?例如,我们需要打开某条消息的详情,或者是某个功能模块。
我们知道,我们打开别的应用是需要设置URL Types,然后通过URL Schemes来打开应用的,同样的,扩展也可以看成是其他应用,这样,我们势必也要为自己的App设置一个URL Types。
首页我们设置一个URL Types
![](https://i-blog.csdnimg.cn/blog_migrate/a92a80f3b7b69446fb1d7aedd88316db.png)
当我们想通过openURL来打开应用时,却发现报错了
![](https://i-blog.csdnimg.cn/blog_migrate/fb6e3aa99cdd86500ca73fde2c20ed64.png)
这是因为扩展不是一个完整的程序,所以它并没没有[UIApplication sharedApplication] 这个对象。
所以Apple给每个UIViewController加了一个extensionContext属性,在我们的宿主App中,这个属性是nil,而在扩展中,我们就可以通过extensionContext来执行跳转.
我们在点击事件中添加如下代码
[self.extensionContext openURL:[NSURL URLWithString:@"AppExtension://add"] completionHandler:nil];
既然有跳转,肯定涉及到传处理了,我们在AppDelegate里处理消息
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { // 可以先回到应用首页,在跳转 if ([url.absoluteString hasPrefix:@"AppExtension"]) { if ([url.absoluteString hasSuffix:@"add"]) { // do something } else if ([url.absoluteString containsString:@"detail"]){ // do something } } return YES; }
5、补充:读取xib文件、扩展中支持三方框架、参数传递、扩展Widget高度、上架注意事项等
a、读取xib文件
如果cell样式是xib,并出现读取错误问题,可以使用下面代码尝试
NSBundle *bundle = [NSBundle bundleForClass:[TodayItemView class]];
NSArray *cells = [bundle loadNibNamed:@"TodayItemView" owner:nil options:nil]; TodayItemView *itemView = cells.firstObject;
b、扩展中支持三方框架
如果扩展中使用到三方框架,则在Podfile中添加下面代码,并且update
target :'MyTodayWidget' do
platform :ios, '8.0' pod 'AFNetworking', '~> 3.1.0' end
c、参数传递
如果需要传递多个参数,可以参考下面代码尝试
NSString *urlString = [NSString stringWithFormat:@"AppExtension://markCode=%@&code=%@&yesclose=%@&stockName=%@",market_stockCode,stockCode,preclose_px,[stockName stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
[self.extensionContext openURL:[NSURL URLWithString:urlString] completionHandler:nil];
⚠️:url中不能出现中文,需要进行UTF-8转换,上面例子中,我将中文名称进行了转换,你也可以将urlString整体进行转换
d、url解析(接上面c)
如果url解析有问题,可以参考下面代码尝试
// 将url转为http形式
NSString *tmpUrlString = [url.absoluteString stringByReplacingOccurrencesOfString:@"AppExtension://" withString:@"http://xxx?"];
NSURLComponents *components = [NSURLComponents componentsWithString:tmpUrlString]; NSArray* queryItems = components.queryItems; NSMutableDictionary* queryItemDict = [NSMutableDictionary dictionary]; // 将value和name转换为字典 for (NSURLQueryItem* item in queryItems) { [queryItemDict setObject:item.value forKey:item.name]; }
e、扩展Widget高度
系统默认的高度为110,如果想要在通知中心扩展高度,可以使用下面代码尝试
- (void)viewDidLoad {
[super viewDidLoad];
// 将小部件展现模型设置为可展开
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
}
完成下面代理
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
if (activeDisplayMode == NCWidgetDisplayModeExpanded) {
// 设置展开的新高度
self.preferredContentSize = CGSizeMake(0, NewHeight); }else{ self.preferredContentSize = maxSize; } }
⚠️:使用3DTouch唤出的弹窗依旧是110,上面代码只是改变了通知中心的高度
f、上架注意事项
如果出现打包,或上架失败,可以尝试下面步骤
- 扩展和target中的应用包都选自动管理签名和证书
![](https://i-blog.csdnimg.cn/blog_migrate/d53f3c19f8e74aa572c09f436666ee1c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/458a9a92f7f7894fa20ab91582ecfc8c.png)
- 项目中配置正确的证书
![](https://i-blog.csdnimg.cn/blog_migrate/a2bd93b03be5fb0ba6cac657bf7ce36d.png)
参考地址