iOS 8 应用扩展教程
已发表: 2022-03-11以前很少有人尝试过(看看这个),但苹果的第一款 iPhone 定义了智能手机和移动操作系统的外观。 苹果在硬件和用户体验方面取得了令人难以置信的突破。 然而,我们经常忘记,它们还为移动操作系统的工作方式以及智能手机应用程序的开发方式设定了标准。
在应用程序之间建立混凝土墙,使它们完全隔离并且彼此不知道,是保证它们安全和保护数据的最佳方法。 所有活动都受到 iOS 的密切监控,应用程序只能在其范围之外执行少数操作。
“禁欲是最好的保护!” - 但这其中的乐趣在哪里?
他们花了一段时间; 如果你问我,时间太长了,但 Apple 决定使用 iOS 8 来找点乐子。 iOS 8 引入了一个名为 App Extensions 的新概念。 这个新功能并没有打破应用程序之间的隔阂,但它打开了几扇门,在一些应用程序之间提供了温和而切实的联系。 最新的更新为 iOS 开发者提供了自定义 iOS 生态系统的选项,我们也渴望看到这条道路的开放。
什么是 iOS 8 应用扩展以及它们如何工作?
简单来说,iOS 8 App Extensions 提供了一种与您的应用程序交互的新方法,无需启动它或将其显示在屏幕上。
正如预期的那样,Apple 确保他们掌握一切,因此您的应用程序可以提供一些新的入口点:
- 今天(也称为小部件)- 显示在通知中心的今天视图中的扩展显示简要信息并允许执行快速任务。
- 共享 - 一种扩展,使您的应用能够与社交网络和其他共享服务上的用户共享内容。
- 操作 - 一个扩展,允许在操作表中创建自定义操作按钮,以让用户查看或转换源自主机应用程序的内容。
- 照片编辑 - 允许用户在照片应用程序中编辑照片或视频的扩展程序。
- Document Provider - 用于允许其他应用访问由您的应用管理的文档的扩展。
- 自定义键盘 - 替换系统键盘的扩展。
应用程序扩展不是独立的应用程序。 他们正在提供应用程序的扩展功能(可以从其他应用程序访问,称为主机应用程序),旨在提高效率并专注于单个任务。 它们有自己的二进制文件、自己的代码签名和自己的元素集,但作为包含应用程序二进制文件的一部分通过 App Store 交付。 一个(包含)应用程序可以有多个扩展。 一旦用户安装了具有扩展的应用程序,它们将在 iOS 上可用。
让我们看一个示例:用户使用 Safari 找到一张图片,点击分享按钮并选择您的应用程序扩展进行分享。 Safari 与 iOS 社交框架“对话”,后者加载并呈现扩展。 扩展程序的代码运行,使用系统的实例化通信通道传递数据,一旦任务完成 - Safari 就会关闭扩展程序视图。 此后不久,系统终止进程,您的应用程序从未显示在屏幕上。 但它完成了图片分享功能。
iOS,使用进程间通信,负责确保宿主应用程序和应用程序扩展可以一起工作。 开发人员使用扩展点和系统提供的高级 API,因此他们不必担心底层的通信机制。
生命周期
应用扩展的生命周期与 iOS 应用不同。 主机应用程序启动扩展程序的生命周期作为对用户操作的响应。 然后系统实例化应用程序扩展并在它们之间建立通信通道。 扩展程序的视图使用主机应用程序请求中收到的项目显示在主机应用程序的上下文中。 一旦显示扩展的视图,用户就可以与之交互。 为了响应用户的操作,扩展程序通过立即执行/取消任务来完成主机应用程序的请求,或者在必要时启动后台进程来执行它。 在那之后,宿主应用程序会拆除扩展程序的视图,并且用户返回到宿主应用程序中的先前上下文。 一旦该过程完成,执行此过程的结果可能会返回到主机应用程序。 扩展程序通常在完成从主机应用程序接收到的请求(或启动后台进程以执行它)后很快终止。
系统从宿主应用打开用户操作的扩展,扩展显示 UI,执行一些工作,并将数据返回给宿主应用(如果这适合扩展的类型)。 包含的应用程序在其扩展程序运行时甚至没有运行。
创建应用程序扩展 - 使用 Today 扩展的动手示例
今日扩展,也称为小部件,位于通知中心的今日视图中。 它们是为用户呈现最新内容(例如显示天气状况)或执行快速任务(例如在待办事项列表应用程序的小部件中标记已完成的事情)的好方法。 我必须在这里指出,不支持键盘输入。
让我们创建一个 Today 扩展,它将显示来自我们应用程序的最新信息(GitHub 上的代码)。 为了运行此代码,请确保您已经(重新)为项目配置了 App Group(选择您的开发团队,请记住 App Group 名称必须是唯一的并遵循 Xcode 的说明)。
创建一个新的小部件
正如我们之前所说,应用程序扩展不是独立的应用程序。 我们需要一个包含应用程序,我们将在其上构建应用程序扩展。 一旦我们有了包含应用程序,我们选择通过导航到 File -> New -> Target 到 Xcode 添加一个新目标。 从这里我们为我们的新目标选择模板以添加今日扩展。
在下一步中,我们可以选择我们的产品名称。 该名称将出现在通知中心的“今天”视图中。 在此步骤中也可以选择在 Swift 和 Objective-C 之间选择语言。 完成这些步骤后,Xcode 创建了一个 Today 模板,它为主体类(名为TodayViewController
)提供了默认的头文件和实现文件,其中包含Info.plist
文件和一个接口文件(一个故事板或 .xib 文件)。 默认情况下, Info.plist
文件如下所示:
<key>NSExtension</key> <dict> <key>NSExtensionMainStoryboard</key> <string>MainInterface</string> <key>NSExtensionPointIdentifier</key> <string>com.apple.widget-extension</string> </dict>
如果您不想使用模板提供的情节提要,请删除NSExtensionMainStoryboard
键并添加NSExtensionPrincipalClass
键,并将视图控制器的名称作为值。
Today 小部件应该:
- 确保内容始终是最新的
- 适当地响应用户交互
- 表现良好(iOS 小部件必须明智地使用内存,否则它们将被系统终止)
共享数据和共享容器
应用程序扩展及其包含的应用程序都可以访问其私有定义的共享容器中的共享数据 - 这是包含应用程序和扩展程序之间间接通信的一种方式。
你不只是喜欢 Apple 如何让这些东西变得如此“简单”吗? :)
通过NSUserDefaults
共享数据既简单又常见。 默认情况下,扩展及其包含的应用程序使用单独的NSUserDefaults
数据集,并且不能访问彼此的容器。 为了改变这种行为,iOS 引入了App Groups 。 在包含应用程序和扩展程序上启用应用程序组后,不要使用[NSUserDefaults standardUserDefaults]
使用[[NSUserDefaults alloc] initWithSuiteName:@"group.yourAppGroupName"]
来访问相同的共享容器。
更新小部件
为了确保内容始终是最新的,Today 扩展提供了一个 API 用于管理小部件的状态和处理内容更新。 系统偶尔会捕获小部件视图的快照,因此当小部件变得可见时,会显示最近的快照,直到它被实时版本的视图替换。 符合NCWidgetProviding
协议对于在拍摄快照之前更新小部件的状态非常重要。 一旦小部件接收到widgetPerformUpdateWithCompletionHandler:
调用,小部件的视图应该使用最新的内容进行更新,并且应该使用以下常量之一调用完成处理程序来描述更新的结果:

-
NCUpdateResultNewData
- 新内容需要重绘视图 NCUpdateResultNoDate
- 小部件不需要更新NCUpdateResultFailed
- 更新过程中发生错误
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { // Perform any setup necessary in order to update the view. // If an error is encountered, use NCUpdateResultFailed // If there's no update required, use NCUpdateResultNoData // If there's an update, use NCUpdateResultNewData [self updateTableView]; completionHandler(NCUpdateResultNewData); }
控制小部件何时可见
要控制小部件何时显示,请使用NCWidgetController
类中的setHasContent:forWidgetWithBundleIdentifier:
方法。 此方法将让您指定小部件内容的状态。 它可以从小部件或其包含的应用程序(如果它处于活动状态)中调用。 您可以将NO
或YES
标志传递给此方法,定义小部件内容是否已准备好。 如果内容尚未准备好,iOS 将不会在今日视图打开时显示您的小部件。
NCWidgetController *widgetController = [[NCWidgetController alloc] init]; [widgetController setHasContent:YES forWidgetWithBundleIdentifier:@"com.your-company.your-app.your-widget"];
从小部件打开包含的应用程序
Today 小部件是唯一可以通过调用openURL:completionHandler:
方法请求打开其包含应用程序的扩展。 为了确保包含应用程序以在用户当前任务的上下文中有意义的方式打开,应定义自定义 URL 方案(小部件和包含应用程序都可以使用)。
[self.extensionContext openURL:[NSURL URLWithString:@"customURLsheme://URLpath"] completionHandler:nil];
用户界面注意事项
在设计你的小部件时,利用UIVisualEffectView
类,记住应该模糊/充满活力的视图必须添加到contentView
而不是直接添加到UIVisualEffectView
。 Widgets(符合NCWidgetProviding
协议)应该在viewWillAppear:
中加载缓存状态,以便匹配上一个viewWillDisappear:
中的视图状态,然后在到达时平滑过渡到新数据,这不是普通视图的情况控制器(UI 在viewDidLoad
中设置,并在viewWillAppear
中处理动画和加载数据)。 小部件应设计用于执行任务或单击即可打开包含的应用程序。 键盘条目在小部件中不可用。 这意味着不应使用任何需要文本输入的 UI。
无法将垂直和水平滚动条添加到小部件中。 或者更准确地说,可以添加滚动视图,但滚动不起作用。 Today 扩展中滚动视图中的水平滚动手势将被通知中心拦截,这将导致从 Today 滚动到通知中心。 垂直滚动今日扩展内的滚动视图将被今日视图的滚动中断。
技术说明
在这里,我将指出一些在创建 App Extension 时要牢记的重要事项。
所有扩展共有的功能
以下各项适用于所有扩展:
sharedApplication 对象不受限制:应用程序扩展无法访问 sharedApplication 对象,或使用与该对象相关的任何方法。
摄像头和麦克风不受限制:应用扩展程序无法访问设备上的摄像头或麦克风(但并非所有硬件元素都如此)。 这是某些 API 不可用的结果。 要访问应用扩展中的某些硬件元素,您必须检查其 API 是否可用于应用扩展(使用上述 API 可用性检查)。
大多数后台任务是不受限制的:应用扩展不能执行长时间运行的后台任务,除了启动上传或下载,这将在下面讨论。
AirDrop 不受限制:应用程序扩展无法使用 AirDrop 接收(但可以发送)数据。
在后台上传/下载
可以在后台执行的一项任务是使用NSURLSession object
进行上传/下载。
上传/下载任务启动后,扩展程序可以完成宿主应用程序的请求并终止,而不影响任务的结果。 如果在后台任务完成时扩展没有运行,系统会在后台启动包含应用程序,并调用应用程序的委托方法application:handleEventsForBackgroundURLSession:completionHandler:
扩展程序启动后台NSURLSession
任务的应用程序必须设置一个共享容器,包含应用程序及其扩展程序都可以访问该容器。
确保为包含的应用程序及其每个应用程序扩展创建不同的后台会话(每个后台会话应该有一个唯一的标识符)。 这很重要,因为一次只有一个进程可以使用后台会话。
行动与分享
从编码人员的角度来看,Action 和 Share 扩展之间的区别并不完全清楚,因为在实践中它们非常相似。 Xcode 的共享扩展目标模板使用SLComposeServiceViewController
,它提供了可用于社交共享的标准撰写视图 UI,但这不是必需的。 共享扩展也可以直接从 UIViewController 继承以实现完全自定义的设计,就像 Action 扩展可以从SLComposeServiceViewController
继承一样。
这两种类型的扩展之间的区别在于它们的使用方式。 使用 Action 扩展,您可以构建没有自己的 UI 的扩展(例如,用于翻译所选文本并将翻译返回给主机应用程序的扩展)。 共享扩展可让您直接从主机应用程序共享评论、照片、视频、音频、链接等。 UIActivityViewController
驱动 Action 和 Share 扩展,其中 Share 扩展在顶行显示为彩色图标,而操作扩展在底行显示为单色图标(图 2.1)。
禁止的 API
不能使用在头文件中标有NS_EXTENSION_UNAVAILABLE
宏或类似宏不可用的 API(例如:iOS 8 中的 HealthKit 和 EventKit UI 框架不适用于任何应用程序扩展)。
如果您在应用程序和扩展程序之间共享代码,您必须记住,即使引用应用程序扩展程序不允许的 API 也会导致您的应用程序被 App Store 拒绝。 您可以选择通过将共享类重新分解为层次结构来处理此问题,具有共同的父类和针对不同目标的不同子类。另一种方法是通过#ifdef
检查使用预处理器。 因为仍然没有内置的目标条件,你必须创建你自己的。
另一个不错的方法是创建自己的嵌入式框架。 只需确保它不包含任何不可用于扩展的 API。 要配置应用程序扩展以使用嵌入式框架,请导航到目标的构建设置并将“仅需要应用程序扩展安全 API”设置设置为是。 在配置 Xcode 项目时,在 Copy Files 构建阶段,必须选择“Frameworks”作为嵌入式框架的目标。 如果您选择“SharedFrameworks”目的地,您的提交将被 App Store 拒绝。
关于向后兼容性的说明
尽管应用程序扩展仅在 iOS 8 之后才可用,但您可以使包含的应用程序可用于之前的 iOS 版本。
Apple 人机界面合规性
在设计应用程序扩展时,请牢记 Apple 的 iOS 人机界面指南。 无论您的包含应用程序支持哪种设备,您都必须确保您的应用程序扩展是通用的。 要确保应用程序扩展是通用的,请在 Xcode 中使用目标设备系列构建设置,指定“iPhone/iPad”值(有时称为通用)。
结论
应用程序扩展无疑在 iOS 8 中具有最明显的影响。由于 79% 的设备已经在使用 iOS 8(根据 App Store 于 2015 年 4 月 13 日进行的测量),应用程序扩展是应用程序应该利用的令人难以置信的功能。 通过结合 API 的限制以及在扩展程序及其包含的应用程序之间共享数据的方式,Apple 似乎设法在不损害其安全模型的情况下解决了对该平台的最大抱怨之一。 第三方应用程序仍然无法直接相互共享数据。 虽然这是一个非常新的概念,但看起来很有前途。