Today Extension(widget)看我就够了

一、介绍

1、app extension、containing app及host app

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。

2、widget的生命周期

extension是针对某项功能的扩展,不是单独存在的。它的生命周期与app的生命周期的各自独立,为两个进程。

  • 开始:在用户通过host app点击extension时,系统实例化extension,extension的生命周期开始。

  • 执行:extension启动后,执行自己的任务。

  • 消失:任务执行结束或用户通过host app结束任务,又或者系统会将其杀掉,extension的生命周期结束。

    Today Extension(widget)看我就够了_第1张图片

二、widget原理

1、widget和containing app
  • widget通过openURL的方式启动containing app

  • 数据交互,使用使用NSUserDefaults或者NSFileManager

    Today Extension(widget)看我就够了_第2张图片

2、widget刷新机制

由于,系统会自动保存widget的快照,直到新的数据或UI刷新。短时间内重复展示widget页面,widget的执行路径直接只会执行viewWillAppear方法。而在第一次下拉或超过2s再显示widget页面时,widget的执行路径是viewDidLoad->viewWillAppear

3、widget和app共享资源
  • 共享数据

    开启App Groups, 然后使用 app groups来实现二者共享。开启后 NSUserDefaults 或者 NSFileManager 利用 App Groups 共享数据

  • 代码共享

    (1)在Build Phases下的Compile Sources中添加需要的文件,注意添加想要使用的.m文件的同时,也需要添加该文件中引入的类的.m文件,适用于共用代码少的情况

    (2)创建framework。可参考博文中framework创建部分、Sharing Code with an Embedded Framework

三、遇到的问题及解决方案

1、在widget中,右上方的展开、折叠具体实现

在NSExtensionContext中,新添了widgetLargestAvailableDisplayMode属性,来确认当前widget是展开还是折叠状态。所以,先在viewWillAppear中设置widget的mode为展开。
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
    然后,就是展开和折叠的处理了。在NCWidgetProviding协议中,新添了这么个方法

-(void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize 设置不同模式下的widget展现内容的高度。

2、widget处于折叠状态时,然后系统2s刷新后,第一次点击展开失效

这个问题是preferredContentSize的设置问题,确认widget的mode之前,谨慎设置这个值。之后,在点击展开、折叠按钮修改了mode之后,一定要设置对应model的preferredContentSize

3、界面的UI的更新要在主线程操作

dispatch_async(dispatch_get_main_queue(), ^{ //...........; });

4、widget和app共用数据

使用NSUserDefaults或者NSFileManager(ios7,NSFileManager提供了containerURLForSecurityApplicationGroupIdentifier方法,可实现app groups共享数据)

5、widget横竖屏兼容

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方法必然执行的)

6、containing app能够控制extension的出现和隐藏

如果隐藏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中都可以调用,需要导入头文件。

7、widget不显示

检查下 widget 的 Deployment Target 是不是小于等于你的真机版本

8、设置extension的名字

在extension target下的info.plist下设置

9、3DTouch效果增加widget,在app图标上touch之后展示一个widget

在app 的 info.plist 文件中增加一组key-value,即Home Screen Widget–widget的bundleID

10、天气、新闻类app,当数据发生改变需要及时更改widget页面,展示最新数据。

我们首先想到的是使用 Timer 来实现,根据与后台协商好的时间,定时获取数据更新widget那么我们在什么时机下触发、销毁timer,由于widget 中controller 特殊的生命周期,为了适应用户不同的操作,我们在控制器的viewWillAppear方法中出发timer,在viewDidDisappear方法销毁timer。

11、国际化
  • widget名字的国际化:New file选择Strings file,然后更名为InfoPlist,生成InfoPlist.strings文件后点击右边中Localize…,在弹窗中选择自己支持的多语言即可;在生成的多语言文件中写app的名称

    CFBundleDisplayName = “简体”/“繁體”/“English”

  • widget内容多语言:New file选择Strings file,然后更名为Localizable,生成Localizable.strings文件后点击右边中Localize…,在弹窗中选择自己支持的多语言即可;在生成的多语言文件中写支持的多语言即可。

12 widget隐藏

​ NCWidgetController *widgetController = [NCWidgetController widgetController];

​ [widgetController setHasContent:NO forWidgetWithBundleIdentifier:WB_BUNDLE_ID_FOR_HOTWEIBO_WIDGET_STRING];

添加这行代码后,在app第一次运行的时候,widget会隐藏,但是在设置页面还会有,但是即使再移除、在添加,在today extension界面也不会显示该widget。

扩展

1、如果Container app数据发生改变需要Extension马上更新应该怎样操作?
  • 方案研究如下

(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)

你可能感兴趣的:(iOS之系统功能)