app extension
extension不能单独存在,必须有一个包含它的containing app,它有一个包含在app bundle中的独立bundle,extension的bundle后缀名是.appex。其生命周期也和普通app不同。
extension需要用户手动激活,不同的extension激活方式也不同。Today中的widget需要在Today中激活和关闭;Custom keyboard需要在设置中进行相关设置;Photo Editing需要在使用照片时在照片管理器中激活或关闭;Storage Provider可以在选择文件时出现;Share和Action可以在任何应用里被激活,但前提是开发者需要设置Activation Rules,以确定extension需要在合适出现。
containing app
extension,不能单独存在,必须依赖主app,主app即containing app, extension会随着containing app的安装/卸载而安装/卸载。
host app
能够调起extension的app被称为host app,比如widget的host app就是Today。
extension和host app之间可以通过extensionContext属性直接通信(该属性是UIViewController类别)实际上extension和host app之间是通过IPC(interprocess communication)实现的。 containing app和host app之间没有任何直接关系,也从来不需要通信。extension和containing app之间的关系最复杂,因为extension依赖与containing app。
extension是针对某项功能的扩展,不是单独存在的。它的生命周期与app的生命周期的各自独立,为两个进程。
开始:在用户通过host app点击extension时,系统实例化extension,extension的生命周期开始。
执行:extension启动后,执行自己的任务。
消失:任务执行结束或用户通过host app结束任务,又或者系统会将其杀掉,extension的生命周期结束。
由于,系统会自动保存widget的快照,直到新的数据或UI刷新。短时间内重复展示widget页面,widget的执行路径直接只会执行viewWillAppear方法。而在第一次下拉或超过2s再显示widget页面时,widget的执行路径是viewDidLoad->viewWillAppear。
共享数据
开启App Groups, 然后使用 app groups来实现二者共享。开启后 NSUserDefaults 或者 NSFileManager 利用 App Groups 共享数据
代码共享
(1)在Build Phases下的Compile Sources中添加需要的文件,注意添加想要使用的.m文件的同时,也需要添加该文件中引入的类的.m文件,适用于共用代码少的情况。
(2)创建framework。可参考博文中framework创建部分、Sharing Code with an Embedded Framework
在NSExtensionContext中,新添了widgetLargestAvailableDisplayMode属性,来确认当前widget是展开还是折叠状态。所以,先在viewWillAppear中设置widget的mode为展开。
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
然后,就是展开和折叠的处理了。在NCWidgetProviding协议中,新添了这么个方法
-(void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize
设置不同模式下的widget展现内容的高度。
这个问题是preferredContentSize的设置问题,确认widget的mode之前,谨慎设置这个值。之后,在点击展开、折叠按钮修改了mode之后,一定要设置对应model的preferredContentSize。
dispatch_async(dispatch_get_main_queue(), ^{ //...........; });
使用NSUserDefaults或者NSFileManager(ios7,NSFileManager提供了containerURLForSecurityApplicationGroupIdentifier方法,可实现app groups共享数据)
widget默认为竖屏,但我们不能安照app内处理横竖屏的适配方式来处理widget的横竖屏适配。我们可以使用下面的方式来处理横竖屏的问题。之后根据全局属性isLandscape来判读是否为横屏,然后设置横竖屏的UI布局
//横屏/竖屏重新布局
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator
{
CGSize screenSize = [UIScreen mainScreen].bounds.size;
if (screenSize.width > screenSize.height) {
self.isLandscape = YES;
}else{
self.isLandscape = NO;
}
}
也可以在- (void)viewWillLayoutSubviews
或者- (void)viewDidLayoutSubviews
方法中判断是否为横屏(这两个方法都是viewWillAppear方法必然执行的)
如果隐藏extension,用户在Today的展示页面看不到隐藏的extension,但是在编辑设置页面可以看到extension还是存在的,这样容易给用户错觉。
//让隐藏的插件重新显示
-(void)showTodayExtension
{
[[NCWidgetController widgetController] setHasContent:YES forWidgetWithBundleIdentifier:@"com.app.extension"];
}
//隐藏插件
-(void)hiddeTodayExtension
{
[[NCWidgetController widgetController] setHasContent:NO forWidgetWithBundleIdentifier:@"com.app.extension"];
}
注意:
调用隐藏方法之后,需要调用展示方法才可以再次展示该extension
该方法在App Extension和Containing App中都可以调用,需要导入
头文件。
检查下 widget 的 Deployment Target 是不是小于等于你的真机版本
在extension target下的info.plist下设置
在app 的 info.plist 文件中增加一组key-value,即Home Screen Widget–widget的bundleID
我们首先想到的是使用 Timer 来实现,根据与后台协商好的时间,定时获取数据更新widget那么我们在什么时机下触发、销毁timer,由于widget 中controller 特殊的生命周期,为了适应用户不同的操作,我们在控制器的viewWillAppear方法中出发timer,在viewDidDisappear方法销毁timer。
widget名字的国际化:New file选择Strings file,然后更名为InfoPlist,生成InfoPlist.strings文件后点击右边中Localize…,在弹窗中选择自己支持的多语言即可;在生成的多语言文件中写app的名称
CFBundleDisplayName = “简体”/“繁體”/“English”
widget内容多语言:New file选择Strings file,然后更名为Localizable,生成Localizable.strings文件后点击右边中Localize…,在弹窗中选择自己支持的多语言即可;在生成的多语言文件中写支持的多语言即可。
NCWidgetController *widgetController = [NCWidgetController widgetController];
[widgetController setHasContent:NO forWidgetWithBundleIdentifier:WB_BUNDLE_ID_FOR_HOTWEIBO_WIDGET_STRING];
添加这行代码后,在app第一次运行的时候,widget会隐藏,但是在设置页面还会有,但是即使再移除、在添加,在today extension界面也不会显示该widget。
(1)NSNotificationCenter 仅在 app 内部起作用,在 apps 之间不起作用。
(2) UserDefaults(KVO)的方式是在main app 与 extension 之间起作用,apps之间无用。
(3)使用 NSFileCoordinator 和 NSFilePresenter 实现,虽可实现但有点复杂。
(4)使用 CFNotificationCenterGetDarwinNotifyCenter 实现。
原理
CFNotificationCenterGetDarwinNotifyCenter 是 core foundation中系统级别的通知,使用它需要用到Toll-Bridge的知识与CoreFoundation相关的类进行桥接。
参考链接
MMWormhole第三方库,用于Container app 与 entension 之间传递数据信息
CFNotificationCenterGetDarwinNotifyCenter使用方法
App与Extensions间通信共享数据
Entitlement Key Reference
App Extension Programming Guide(Today Extension)