iOS 中的扩展有多种,包括:Today,Share,Action,PhotoEditing,FinderSync,自定义键盘等等。这里要说的类型是 Today,就是在通知中心“今天”里面添加的 widget
对于 Today 插件,它应该:
- 确保内容永远是最新的
- 正确响应用户的操作
- 高效运行(特别是iOS插件必须合理使用内存,否则系统可能终止插件的运行)
####新建 Widget Target
Extesion 都是依附于某个主体应用程序(containing app)中的一个单独的二进制包。所以它必须在现有的工程中来创建。它与主体程序之间可以通过某种方式间接通信,下面会提到。
通过 File->New->Target 在弹出菜单中选择 Today Extension 模板
新建完成后工程目录中会自动出现一个文件夹,里面有一些默认生成的文件:
- TodayViewController: widget 的主视图;
- MainInterface.storyboard: 对应 TodayViewController 的布局文件;
- Info.plist: widget 的配置文件
在 Info.plist 中有一个 key 指定了 Extension 的一些基本信息:
如果不想使用 storyBoard 文件来管理布局,那么就将 NSExtensionMainStoryboard
这个 key 移除,然后添加 key NSExtensionPrincipalClass
,并且用 controller 的类名作为 value:
但是我这样做的话程序会报错,至今仍未找到原因。无奈只能将 storyBoard 留下来先
####编写 widget 布局
Today 的视图有限,所以我们的 widget 应该越小巧玲珑越好。况且本来 widget 就不应该太过繁重。widget 应该适应 Today 视图的宽度,通过增加高度来显示更多内容。
要控制 widget 的内容高度,可以通过两种方法,第一种是通过 AutoLayout;第二种则是用:
1 | self.preferredContentSize = CGSizeMake(SCREEN_SIZE.width, HEIGHT); |
另外,NCWidgetProviding
协议里面的一个方法也会影响内容布局:
1 | - (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets |
####数据传输
方式一:自定义 URL Scheme,在 URL 中传递参数
- 在项目对应的 Target 中的 Info 页,添加一个 Scheme。Identifier 为自己项目的 Bundle Identifier;在 URLSchemes 一栏里填上自己想要的名称。
- 通过 myappscheme://?foo=1&bar=2 这种 URL 方式来唤起自己的 app,并且可以在 AppDelegate 中的回调捕获到这个 URL,然后便可以对 URL 中的参数进行分析处理:
1
2
3
4- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
// Do something with the url here
}
方法二:使用 NSUserDefault
widget 与 containing App 共享一个 NSUserDefault。不过要注意的是,这里指的不是我们通常所用的标准 NSUserDefault。在 widget 中的 UserDefault 与 Containing App 中的 UserDefaul 是不一致的,这是一个坑。我们需要在苹果开发者网站里面新建一个 group,然后用这个 group 的名字组建一个 Suite Name 来初始化一个 NSUserDefault:
1 | [[NSUserDefaults alloc] initWithSuiteName:@”group.com.mycompany.myapp”] |
这篇文章可以让你更加详细地了解这部分。
####UIVbrancyEffect
它是跟 UIBlurEffect 一起随 iOS8 发布的。通常情况下它都要配合 UIBlurEffect 来一起使用。通常情况下会这样使用:
1 | - (void)setEffect |
> vibrancy effect 依赖于颜色。任何添加到 contentView 的 subView 都需要实现 `tintColorDidChange` 方法,并且根据需要来更新自己的外观。UIImageView 对象有一种渲染模式 `UIImageRenderingModeAlwaysTemplate` 来进行自动更新,UILabel 对象也是。
而在开发 Today Widget 的时候,想达到下图的中效果该怎么办呢。我们无法获取通知中心背景的 Blur Effect。
NotificationCenter frameWork 给我们提供了一个 Category,看一眼方法名就懂:
1 | @interface UIVibrancyEffect (NotificationCenter) |
于是可以在 widget 的 todayViewController 中这样来添加 effectView
1 | - (void)viewDidLoad { |
这里有个小坑:当我直接将一个 button 添加到 contentView 中,button 的 title 会“显示不出来”。这是因为 button.titleLable 中的文字拥有跟 button 一样的 vibrancyEffect,如果给 button 的背景颜色设置一个 alpha 值,就可以看到 title:
那么问题来了,想要实现上图中 Chrome 浏览器 widget 的 button 一样的效果该怎么办呢。
用 drawInRect:
等函数来自己绘制吧。Github 上已经有人实现了这种风格的 button:AYVibrantButton。里面的 button title 跟 image 都是用方法来绘制的。看来貌似只有这个方法了哦。
####更新插件状态
NCWidgetProviding 协议中有一个方法,会在某些时刻被调用,来给 widget 一个机会来更新自己的 UI,例如截屏的时候。
1 | - (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { |
更新完数据之后要根据数据的更新情况来调用 block 回调。
####widget 的生命周期
NSWidgetProviding.h 中有这样的一段文字:
>翻译翻译:widget 应该在 `viewWillAppear:` 中加载缓存起来的数据,好让它能匹配上次在 `viewWillDisappear` 时的状态,这样在新数据到来的时候就可以丝滑顺畅地过渡。
在 widget 中的 controller,当它 dismiss 的时候会被 deallocated,所以不可以用 NSArray 来保存数据,可以选择用 NSUserDefault。
####参考资料
以上