添加 iOS 应用扩展
本指南将向您展示如何将 iOS 应用扩展与 Flutter 应用结合使用。
概述
#iOS 应用扩展允许您在 iOS 应用之外扩展功能。您的应用可以显示为主屏幕小部件,或者您可以将应用的部分功能提供给其他应用使用。
在以下示例中,当用户在 iOS 照片应用中选择要共享的照片时,“Example App With Extension”这个 Flutter 应用会显示在照片应用的分享表中。

为您的 Flutter 应用添加 iOS 应用扩展
#如果您想将 Flutter 应用与 iOS 操作系统集成,可以为您的 Flutter 项目添加 iOS 应用扩展。为了实现无缝的工作流程,以下步骤将展示如何为名为 example_app_with_extension 的新 Flutter 应用添加一个 分享 应用扩展,但您也可以从现有项目开始。
- 在控制台中,创建一个名为 - example_app_with_extension的新 Flutter 项目。- flutter create example_app_with_extension
- 在控制台中,打开 - example_app_with_extension项目的 Xcode 工作区。- cd example_app_with_extension && open ios/Runner.xcworkspace
- 在 Xcode 中,添加一个名为 - Share的应用扩展,并将其命名为- ShareExtension。- 在 Xcode 菜单栏中,选择 **文件** > **新建** > **目标**。 
- 添加 **分享扩展**。 
- 在 **名称** 字段中,输入 **ShareExtension**。 
- 点击 Finish(完成)。 
- 在出现的 **激活…方案** 对话框中,选择 **激活**。 
 
- 在 Xcode 中,更改构建过程的顺序。 - 打开 **项目导航器** (**视图** > **导航器** > **项目**)。 
- 在 **项目导航器** 的顶部,选择 **Runner**。 
- 在主窗口的 **TARGETS** 下,选择 **Runner**。 
- 打开 **构建阶段** 选项卡。 
- 将 **嵌入 Foundation 扩展** 拖到 **运行脚本** 的上方。 
 
- 请确保您的 **最低部署** iOS 值设置正确,并且在 **Runner** 和 **ShareExtension** 中都匹配。 - 打开 **项目导航器** (**视图** > **导航器** > **项目**)。 
- 在 **项目导航器** 的顶部,选择 **Runner**。 
- 在主窗口的 **TARGETS** 下,选择 **Runner**。 
- 在 **通用** 选项卡上,检查您的 **最低部署** 下拉菜单值,确保它与 **ShareExtension** > **通用** 选项卡上的值相匹配。 
 
- 在控制台中,运行以下命令以重新构建您的 iOS 应用。 - flutter build ios --config-only
当您添加新的应用扩展时,Xcode 会根据您选择的模板生成示例代码。有关生成的代码和 WidgetKit 的更多信息,请参阅 Apple 的应用扩展文档。
测试 iOS 应用扩展
#在将应用扩展添加到 Flutter 项目后,您可以使用模拟器或物理设备对其进行测试。如果您在调试模式下测试扩展,则必须使用 iOS 模拟器。
以下步骤假定您使用的是 添加 iOS 应用扩展中的示例应用程序和分享扩展。
- 在 Xcode 中,为您的项目添加一个应用扩展。 
- 在控制台中,使用以下命令运行您的 iOS 应用。 - flutter run
- 在模拟器中,测试您的应用扩展。 - 启动支持分享扩展的应用,例如照片应用。 
- 选择一张照片,点击分享按钮,然后点击您应用的分享扩展图标。 
 
- 为您的项目添加一个应用扩展。 
- 在控制台中,以发布模式运行您的 Flutter 应用。 - flutter run --release
- 在您的设备上,测试您的应用扩展。 - 启动支持分享扩展的应用,例如照片应用。 
- 选择一张照片,点击分享按钮,然后点击您应用的分享扩展图标。 
 
与 iOS 应用扩展交互的更多方法
#Flutter 应用与 iOS 应用扩展的交互方式与 UIKit 或 SwiftUI 应用相同。包含的应用和应用扩展不直接通信。当设备用户与扩展交互时,包含的应用可能未运行。应用和您的扩展可以读取和写入共享资源,或使用更高级别的 API 进行通信。
使用更高级别的 API
#某些扩展具有 API。例如,Core Spotlight 框架会索引您的应用,允许用户通过 Spotlight 和 Safari 进行搜索。WidgetKit 框架可以触发主屏幕小部件的更新。
为了简化您的应用与扩展之间的通信,Flutter 插件封装了这些 API。要查找封装了扩展 API 的插件,请查看 利用 Apple 的系统 API 和框架 或在 pub.dev 上搜索。
共享资源
#要在您的 Flutter 应用和应用扩展之间共享资源,请将 Runner 应用目标和扩展目标放在同一个 App Group 中。
添加目标到 App Group
- 在 Xcode 中打开目标设置。 
- 导航到 **签名与功能** 选项卡。 
- 选择 **+ 功能**,然后选择 **App Groups**。 
- 从两个选项中选择要将目标添加到的 App Group。 - 从列表中选择一个 App Group。
- 点击 **+** 添加一个新的 App Group。
 

当两个目标属于同一个 App Group 时,它们可以读取和写入同一个源。选择以下任一数据源:
- 键/值:使用 shared_preference_app_group插件在同一个 App Group 内读取或写入UserDefaults。
- 文件:使用 path_provider插件提供的 App Group 容器路径来 读取和写入文件。
- 数据库:使用 path_provider插件提供的 App Group 容器路径,并使用sqflite插件创建一个数据库。
安排后台更新
#后台任务提供了一种通过代码更新扩展的方式,而不管您的应用状态如何。
要从您的 Flutter 应用调度后台任务,请使用 workmanager 插件。
添加深度链接
#您可能希望将用户从应用扩展引导到 Flutter 应用中的特定页面。要打开应用中的特定路由,您可以使用 深度链接。
添加可滚动列表
#默认情况下,Flutter 视图在 分享 扩展中不处理滚动事件。要支持分享扩展中的可滚动列表,请遵循 这些说明。
在 iOS 应用扩展中打开 Flutter 应用
#您可以通过 FlutterViewController 直接在某些 iOS 应用扩展(如 分享 扩展)中打开您的 Flutter 应用。
在以下示例中,名为 Example App With Extension 的 Flutter 应用在分享扩展中打开,允许用户在应用之间共享内容。

使用以下步骤在 分享 应用扩展中显示 Flutter 应用。在此示例中,应用扩展方案名为 ShareExtension,Flutter 应用方案名为 Runner,Flutter 应用名为 Example App With Extension。
- 如果您还没有为 Flutter 应用添加扩展,请 添加一个扩展。 
- 在控制台中,导航到您的 Flutter 项目目录,然后使用以下命令在 Xcode 中打开您的项目。 - open ios/Runner.xcworkspace
- 在 Xcode 中,禁用用户脚本沙盒。 - 打开 **项目导航器** (**视图** > **导航器** > **项目**)。 
- 在主窗口的 **TARGETS** 下,选择 **ShareExtension**。 
- 打开 **构建设置** 选项卡。 
- 导航到 **构建选项**。 
- 将 **用户脚本沙盒** 设置为 **否**。 
 
- 在 Xcode 中,将预操作添加到 - ShareExtension方案。- 打开 **管理方案** 窗口 (**产品** > **方案** > **管理方案**)。 
- 选择 ShareExtension 方案并进行编辑。 
- 展开 **构建** 选项卡。 
- 选择 **预操作**。 
- 点击 **+**,然后选择 **新建运行脚本操作**。 
- 在 **提供构建设置来自** 下拉列表中,选择 **ShareExtension**。 
- 在 **Shell** 文本字段中,输入: - /bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare
- 点击 **关闭**。 
 
- 在 Xcode 中,共享构建配置。 - 打开 **项目导航器** (**视图** > **导航器** > **项目**)。 
- 在主窗口的 **PROJECT** 下,选择 **Runner**。 
- 打开 **信息** 选项卡。 
- 展开 **配置**。 
- 展开 **Debug**,并将 **ShareExtension** 的值更新为与 **Runner** 的值相匹配。 
- 对 **Profile** 和 **Release** 重复上一步。 
- 完成后,请确保配置看起来与以下内容相似。  
 
- (可选) 在 Xcode 中,根据需要用扩展类替换任何故事板文件。 - 打开 **项目导航器** (**视图** > **导航器** > **项目**)。 
- 选择 **Runner** > **ShareExtension** > **Info**。 
- 展开 **信息属性列表**。 
- 删除 **NSExtensionMainStoryboard** 键。 
- 添加 **NSExtensionPrincipalClass** 键。 
- 为 - NSExtensionPrincipalClass键添加以下值之一:- (Swift) **ShareExtension.ShareViewController**
- (Objective-C) **ShareViewController**
 
 
- 在 Xcode 中,更新 - ShareViewController以使用- FlutterViewController。- 打开 **项目导航器** (**视图** > **导航器** > **项目**)。 
- 选择 **Runner** > **ShareExtension** > **ShareViewController**。 
- 更新 - ShareViewController以使用- FlutterViewController类。
 
import UIKit
import Flutter
class ShareViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        showFlutter()
    }
    func showFlutter() {
        let flutterEngine = FlutterEngine(name: "my flutter engine")
        flutterEngine.run()
        let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        addChild(flutterViewController)
        view.addSubview(flutterViewController.view)
        flutterViewController.view.frame = view.bounds
    }
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.extensionContext?.cancelRequest(
            withError: NSError(domain: Bundle.main.bundleIdentifier!, code: 0))
    }
}@import Flutter;
@import UIKit;
@interface ShareViewController : UIViewController
@end#import "ShareViewController.h"
@import Flutter;
@implementation ShareViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self showFlutter];
}
- (void)showFlutter {
    FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
    [flutterEngine run];
    FlutterViewController *flutterViewController =
            [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self addChildViewController:flutterViewController];
    [self.view addSubview:flutterViewController.view];
    flutterViewController.view.frame = self.view.bounds;
}
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier code:0 userInfo:nil]];
}
@end注册插件
#使用以下步骤为应用扩展注册插件。在此示例中,应用扩展方案名为 ShareExtension,Flutter 应用方案名为 Runner,Flutter 应用名为 Example App With Extension。
- 如果您还没有为 Flutter 应用添加扩展,请 添加一个扩展。 
- 在 Xcode 中,将 - GeneratedPluginRegistrant.m添加到应用扩展目标。- 打开 **项目导航器** (**视图** > **导航器** > **项目**)。 
- 在主窗口的 **TARGETS** 下,选择 **ShareExtension**。 
- 打开 **构建阶段** 选项卡。 
- 展开 **编译源文件**。 
- 点击 **+**。 
- 在“选择要添加的项”对话框的列表中,选择 **GeneratedPluginRegistrant.m**。 
- 点击 **添加**。 
 
- (仅限 Swift) 在 Xcode 中,更新 - SWIFT_OBJC_BRIDGING_HEADER构建设置。- 打开 **项目导航器** (**视图** > **导航器** > **项目**)。 
- 在主窗口的 **TARGETS** 下,选择 **ShareExtension**。 
- 打开 **构建设置** 选项卡。 
- 选择 **所有** 过滤器。 
- 导航到 **Swift 编译器 - 通用**,并将 **Objective-C 桥头文件** 键的值更改为 Runner/Runner-Bridging-Header.h。 
 
- 在 Xcode 中,更新 - ShareViewController的代码以注册- GeneratedPluginRegistrant.h。- 打开 **项目导航器** (**视图** > **导航器** > **项目**)。 
- 选择 **Runner** > **ShareExtension** > **ShareViewController**。 
- 更新 - ShareViewController文件以使用- GeneratedPluginRegistrant.h。
 
// Add this inside `showFlutter()` at the top
GeneratedPluginRegistrant.register(with: flutterEngine)// Add this import at the top
#import "GeneratedPluginRegistrant.h"// Add this after [flutterEngine run]
[GeneratedPluginRegistrant registerWithRegistry:flutterEngine];- (Xcode) 使用模拟器测试您的应用。
约束
#- 您必须使用 iOS 模拟器在调试模式下测试您的扩展。 
- Flutter 对在物理设备上以调试模式运行构建了扩展 UI 的应用扩展的支持不完全,因为物理设备可能会耗尽内存。 
- iOS 应用扩展的内存有限。建议仅在应用扩展支持至少 100MB 内存时修改应用扩展的 UI。 
在 iOS 应用扩展中调用 Dart 代码/渲染 Flutter 内容
#home_widget 包提供了大量功能,包括允许以下操作:
- 使用 Dart 代码在应用扩展中 响应用户输入。 
- 在应用扩展中 将 Flutter 小部件渲染 为图像。 
- 在 iOS 上 保存和检索数据(来自 - UserDefaults)。
其他资源
#有关将应用扩展与 Flutter iOS 应用结合使用的分步说明,请查看 为 Flutter 应用添加主屏幕小部件 Codelab。
要了解有关将 Flutter 屏幕添加到 iOS 应用的各种方法的更多信息,请参阅 将 Flutter 屏幕添加到 iOS 应用。