TodayExtension (Widget)
本文将介绍如何在「今日试图」中添加一个扩展,主要以 iOS10 Widget 为主。首先扩展是附属在一个应用内的,也就是我们不能通过 AppStore 直接下载一个扩展,它一定是和宿主 app 一起安装。扩展有自己独立的生命周期。下面将介绍如何创建一个扩展,如何与宿主 app 进行交互。
demo 地址 : TodayExtension (Widget)
1. 添加 TodayExtendion target
点选菜单 File > New > Target…,在 iOS > Application Extension 可以找到 TodayExtension,命名确认后让 Xcode 自动生成新的 Scheme。这时项目中会有一个和刚刚新建的 target 的同名文件夹,里面包含了TodayViewController.swift
MainInterface.storyboard
info.plist
3个文件。可以看到,在storyboard
里默认添加了一个「Hello Wrold」的 label。现在运行一下项目,一切正常的话,会看到如下图:
关于 widget 内容的展示,在 ViewController 里控制就行,具体想要展示的样子,开发者可以自由完成,这里就不展示了。
2. iOS10 Widget 高度调整
如上图,一个 widget 的高度默认是 110。
- 在右上角显示「展开/折叠」按钮
1 | // 需判断系统版本为10.0以上 |
- 点击「展开/折叠」按钮的回调并控制高度
1 | // 同样需要判断版本为10.0以上 |
3. 使用 App Groups 共享扩展和主应用数据
由于沙盒的限制,我们不能直接在扩展和主应用间共享数据,这时候要用到 App Groups。App Groups 为同一个 vender 的应用或者扩展定义了一组域,在这个域中同一个 group 可以共享一些资源。
- 开启 App Groups
选择主 target 的 Capabilities 选项,找到 App Groups 选项,后面有个开关开启。并添加一个 goups name。这里添加一个group.HelloTodayUserDefault
。然后同样的在扩展的 target 也进行同样的步骤。
- 使用同一 groups 下的 userDefault 进行数据共享
为了方便演示,我在主 target 里添加了一个 textView。textView 的文本修改后,将通过 userDefault 将数据共享给扩展。看下代码:
在宿主 app 中更改了 textView 的值,同步到 group 的 userDefault 中
1 | func textViewDidChange(_ textView: UITextView) { |
在扩展中获取数据
1 | let userDefault = UserDefaults(suiteName: "group.HelloTodayUserDefault") |
4. 扩展和主应用共享代码
一个最简单的方式是把需要公用的代码文件加入扩展 target 的编译文件中,但是这么做不好的地方是,当这些共用的文件越来越多,添加到 target 这种方式将难以管理和维护。iOS8 之后 apple 提供了一个更好的方式,做成 Framework。
点选 File > New > Target…,在 Framework & Library 可以找到 Cocoa Touch Framework,next 命名。
上面的代码里,suiteName 我都是直接使用字符串,这里很不好的就是万一两个地方写的不一样了,就有问题了。接下来我在 taget 里添加一个 swift 文件,用一个常量来记录这两个 key 值。
1 | let groupSuiteName = "group.HelloTodayUserDefault" |
首先在主程序的 ViewController 中 import 刚刚新建的 Framework。这时我将原来的字符串替换为常量,发现 Xcode 会提示未定义之类的。这是由于同一个 module 中默认 internal
访问层级,现在代码处于不同的 module。所以需要更改 swift 文件的权限,在需要在外部访问的地方加上 public
关键字
1 | public let groupSuiteName = "group.HelloTodayUserDefault" |
接下来在 ViewController 中就可以直接使用了:
1 | let userDefault = UserDefaults(suiteName: groupSuiteName) |
同样的步骤,将 Framework 链接到扩展中
1 | let userDefault = UserDefaults(suiteName: groupSuiteName) |
接下来编译可以通过,但是会收到一条警告:
这是由于作为插件,需要严格遵守沙盒限制,一些 API 是不能使用的。避免这个警告,需要在 Framework 中声明使用的是扩展可用的 API。切换到 framework target 中的 General 选项,在 Deployment Info 下面勾选 Allow app extension API only。关于不可用的 API,apple 都标注了 NS_EXTENSION_UNAVAILABLE
。
5. 从扩展进入主程序
扩展提供了 NSExtensionContext
类来与主应用交互,这里使用 open
方法就好了。
这里为了方便演示,我就直接给扩展的 view 添加一个点击手势了。由于这是一个 ViewController,我相信开发者会有很多操作的空间。
1 | override func viewDidLoad() { |
在主程序中添加对应的 URL Scheme:
在 AppDelegate.swift
中捕获打开事件,做出对应操作:
1 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { |
End.