将 Flutter 模块集成到 iOS 项目中

Flutter UI 组件可以作为嵌入式框架逐步添加到现有 iOS 应用程序中。有几种方法可以在现有应用程序中嵌入 Flutter。

  1. 使用 CocoaPods 依赖项管理器和已安装的 Flutter SDK。在这种情况下,每次构建应用程序时都会从源代码编译 flutter_module。(推荐)

  2. 为 Flutter 引擎、已编译的 Dart 代码和所有 Flutter 插件创建框架。在此,您手动嵌入框架,并在 Xcode 中更新现有应用程序的构建设置。这对于不想要求每个开发人员都在本地安装 Flutter SDK 和 Cocoapods 的团队很有用。

  3. 为已编译的 Dart 代码和所有 Flutter 插件创建框架。在 CocoaPods 中使用 Flutter 引擎。使用此选项,在 Xcode 中嵌入应用程序和插件的框架,但将 Flutter 引擎分发为 CocoaPods podspec。这类似于第二个选项,但它提供了一种替代方案来分发大型 Flutter.xcframework。

有关使用采用 UIKit 构建的应用的示例,请参阅 add_to_app 代码示例 中的 iOS 目录。有关使用 SwiftUI 的示例,请参阅 新闻提要应用 中的 iOS 目录。

系统要求

你的开发环境必须满足 Flutter 的 macOS 系统要求,且 已安装 Xcode。Flutter 支持 iOS 12 及更高版本。此外,你还需要 CocoaPods 1.10 或更高版本。

创建 Flutter 模块

若要使用上述任何方法将 Flutter 嵌入到现有应用中,请先创建一个 Flutter 模块。

在命令行中运行

cd some/path/
flutter create --template module my_flutter

Flutter 模块项目在 some/path/my_flutter/ 中创建。如果你使用上面提到的第一个方法,则应在与现有 iOS 应用相同的父目录中创建模块。

在 Flutter 模块目录中,你可以运行与在任何其他 Flutter 项目中相同的 flutter 命令,例如 flutter run --debugflutter build ios。你还可以使用 Flutter 和 Dart 插件在 Android Studio/IntelliJVS Code 中运行模块。此项目包含模块的单视图示例版本,在将其嵌入到现有应用之前,该版本可用于逐步测试代码中仅限 Flutter 的部分。

模块组织

my_flutter 模块目录结构类似于普通 Flutter 应用

my_flutter/
├── .ios/
│   ├── Runner.xcworkspace
│   └── Flutter/podhelper.rb
├── lib/
│   └── main.dart
├── test/
└── pubspec.yaml

将 Dart 代码添加到 lib/ 目录中。

将 Flutter 依赖项添加到 my_flutter/pubspec.yaml 中,包括 Flutter 包和插件。

.ios/ 隐藏子文件夹包含一个 Xcode 工作区,你可以在其中运行模块的独立版本。这是一个用于引导 Flutter 代码的包装器项目,其中包含帮助脚本,以便于使用 CocoaPods 构建框架或将模块嵌入到现有应用中。

将 Flutter 模块嵌入到现有的应用程序中

开发完 Flutter 模块后,可以使用页面顶部描述的方法将其嵌入。

使用 Flutter 会增加应用程序的大小

选项 A - 使用 CocoaPods 和 Flutter SDK 嵌入

此方法要求项目中的每个开发者都本地安装了 Flutter SDK 的版本。每次构建应用程序时,都会从源代码编译 Flutter 模块。只需在 Xcode 中构建应用程序即可自动运行脚本来嵌入 Dart 和插件代码。这允许使用 Flutter 模块的最新版本快速迭代,而无需在 Xcode 外部运行其他命令。

以下示例假定现有的应用程序和 Flutter 模块位于同级目录中。如果目录结构不同,则可能需要调整相对路径。

some/path/
├── my_flutter/
│   └── .ios/
│       └── Flutter/
│         └── podhelper.rb
└── MyApp/
    └── Podfile

如果现有的应用程序 (MyApp) 还没有 Podfile,请在
MyApp 目录中运行 pod init 以创建一个 Podfile。可以在 CocoaPods 入门指南 中找到有关使用 CocoaPods 的更多详细信息。

  1. 将以下行添加到 Podfile

    MyApp/Podfile
    flutter_application_path = '../my_flutter'
    load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  2. 对于需要嵌入 Flutter 的每个 Podfile 目标,请调用 install_all_flutter_pods(flutter_application_path)

    MyApp/Podfile
    target 'MyApp' do
      install_all_flutter_pods(flutter_application_path)
    end
  3. Podfilepost_install 块中,调用 flutter_post_install(installer)

    MyApp/Podfile
    post_install do |installer|
      flutter_post_install(installer) if defined?(flutter_post_install)
    end
  4. 运行 pod install

podhelper.rb 脚本将您的插件、Flutter.frameworkApp.framework 嵌入到您的项目中。

您的应用的 Debug 和 Release 构建配置分别嵌入 Debug 或 Release Flutter 的构建模式。向您的应用添加一个 Profile 构建配置以在 profile 模式下测试。

在 Xcode 中打开 MyApp.xcworkspace。您现在可以使用 ⌘B 构建项目。

选项 B - 在 Xcode 中嵌入框架

或者,您可以通过手动编辑现有的 Xcode 项目来生成必要的框架并将它们嵌入到您的应用程序中。如果您团队的成员无法在本地安装 Flutter SDK 和 CocoaPods,或者您不想在现有应用程序中使用 CocoaPods 作为依赖项管理器,则可以这样做。您必须在每次在 Flutter 模块中进行代码更改时运行 flutter build ios-framework

以下示例假定您希望将框架生成到 some/path/MyApp/Flutter/

flutter build ios-framework --output=some/path/MyApp/Flutter/
some/path/MyApp/
└── Flutter/
    ├── Debug/
    │   ├── Flutter.xcframework
    │   ├── App.xcframework
    │   ├── FlutterPluginRegistrant.xcframework (only if you have plugins with iOS platform code)
    │   └── example_plugin.xcframework (each plugin is a separate framework)
    ├── Profile/
    │   ├── Flutter.xcframework
    │   ├── App.xcframework
    │   ├── FlutterPluginRegistrant.xcframework
    │   └── example_plugin.xcframework
    └── Release/
        ├── Flutter.xcframework
        ├── App.xcframework
        ├── FlutterPluginRegistrant.xcframework
        └── example_plugin.xcframework

在 Xcode 中将生成的框架链接并嵌入到您的现有应用程序中。有多种方法可以做到这一点——使用最适合您项目的那个方法。

例如,你可以将框架从 Finder 中的 some/path/MyApp/Flutter/Release/ 拖到目标的构建设置 > 构建阶段 > 使用库链接二进制文件中。

在目标的构建设置中,将 $(PROJECT_DIR)/Flutter/Release/ 添加到框架搜索路径 (FRAMEWORK_SEARCH_PATHS) 中。

Update Framework Search Paths in Xcode

嵌入框架

生成的动态框架必须嵌入到你的应用中才能在运行时加载。

链接框架后,你应该在目标的常规设置的框架、库和嵌入式内容部分中看到它们。要嵌入动态框架,请选择嵌入和签名

然后它们将出现在构建阶段中的嵌入框架下,如下所示

Embed frameworks in Xcode

现在,您应该可以使用 ⌘B 在 Xcode 中构建项目。

选项 C - 使用 CocoaPods 在 Xcode 和 Flutter 框架中嵌入应用程序和插件框架

或者,您可以通过添加标志 --cocoapods 来生成 Flutter 作为 CocoaPods podspec,而不是将大型 Flutter.xcframework 分发给其他开发者、机器或持续集成系统。这将生成 Flutter.podspec 而不是引擎 Flutter.xcframework。App.xcframework 和插件框架的生成方式如选项 B 中所述。

要生成 Flutter.podspec 和框架,请在 Flutter 模块的根目录中从命令行运行以下命令

flutter build ios-framework --cocoapods --output=some/path/MyApp/Flutter/
some/path/MyApp/
└── Flutter/
    ├── Debug/
    │   ├── Flutter.podspec
    │   ├── App.xcframework
    │   ├── FlutterPluginRegistrant.xcframework
    │   └── example_plugin.xcframework (each plugin with iOS platform code is a separate framework)
    ├── Profile/
    │   ├── Flutter.podspec
    │   ├── App.xcframework
    │   ├── FlutterPluginRegistrant.xcframework
    │   └── example_plugin.xcframework
    └── Release/
        ├── Flutter.podspec
        ├── App.xcframework
        ├── FlutterPluginRegistrant.xcframework
        └── example_plugin.xcframework

使用 CocoaPods 的宿主应用程序可以将其 Podfile 添加到 Flutter

MyApp/Podfile
pod 'Flutter', :podspec => 'some/path/MyApp/Flutter/[build mode]/Flutter.podspec'

按照选项 B 中的说明,将生成的 App.xcframework、FlutterPluginRegistrant.xcframework 和任何插件框架链接并嵌入到您的现有应用程序中。

本地网络隐私权限

在 iOS 14 及更高版本中,在应用程序的调试版本中启用 Dart 多播 DNS 服务,以通过 flutter attach 添加 调试功能,如热重载和 DevTools

执行此操作的一种方法是为每个构建配置维护应用的 Info.plist 的单独副本。以下说明假定默认的调试发布。根据应用的构建配置按需调整名称。

  1. 将应用的Info.plist重命名为Info-Debug.plist。创建其副本并将其命名为Info-Release.plist,并将其添加到 Xcode 项目中。

    Info-Debug.plist and Info-Release.plist in Xcode
  2. Info-Debug.plist中添加键 NSBonjourServices,并将值设置为包含字符串 _dartVmService._tcp 的数组。请注意,Xcode 将此显示为“Bonjour 服务”。

    (可选)添加键 NSLocalNetworkUsageDescription,并将其设置为所需的自定义权限对话框文本。

    Info-Debug.plist with additional keys
  3. 在目标的构建设置中,将Info.plist 文件 (INFOPLIST_FILE) 设置路径从 path/to/Info.plist 更改为 path/to/Info-$(CONFIGURATION).plist

    Set INFOPLIST_FILE build setting

    这将解析为调试中的路径Info-Debug.plist发布中的Info-Release.plist

    Resolved INFOPLIST_FILE build setting

    或者,你可以明确将调试路径设置为Info-Debug.plist,将发布路径设置为Info-Release.plist

  4. 如果Info-Release.plist副本位于目标的构建设置 > 构建阶段 > 复制包资源构建阶段中,请将其移除。

    Copy Bundle build phase

    现在,由调试应用加载的第一个 Flutter 屏幕将提示获取本地网络权限。还可以通过启用设置 > 隐私 > 本地网络 > 你的应用来允许权限。

    Local network permission dialog

Apple Silicon (arm64 Mac)

在 Apple Silicon (M1) Mac 上,主机应用为 arm64 模拟器构建。虽然 Flutter 支持 arm64 模拟器,但某些插件可能不支持。如果你使用其中一个插件,你可能会看到编译错误,例如架构 arm64 的未定义符号,你必须从主机应用中的模拟器架构中排除 arm64

在主机应用目标中,找到排除的架构 (EXCLUDED_ARCHS) 构建设置。单击右箭头展开指示符图标以展开可用的构建配置。将鼠标悬停在调试上,然后单击加号图标。将任何 SDK更改为任何 iOS 模拟器 SDK。将 arm64 添加到构建设置值。

Set conditional EXCLUDED_ARCHS build setting

正确完成后,Xcode 将向你的project.pbxproj文件添加 "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;

对任何 iOS 单元测试目标重复。

开发

您现在可以将Flutter 屏幕添加到现有应用程序。