向 iOS 应用添加 Flutter 屏幕
本指南介绍如何将单个 Flutter 屏幕添加到现有的 iOS 应用中。
启动 FlutterEngine 和 FlutterViewController
#要从现有的 iOS 应用启动 Flutter 屏幕,您需要启动一个 FlutterEngine
和一个 FlutterViewController
。
FlutterEngine
的生命周期可能与您的 FlutterViewController
相同,也可能比您的 FlutterViewController
更长。
有关预热引擎的延迟和内存权衡的更多分析,请参阅加载顺序和性能。
创建 FlutterEngine
#您创建 FlutterEngine
的位置取决于您的宿主应用程序。
在此示例中,我们在名为 FlutterDependencies
的 SwiftUI Observable
对象内创建一个 FlutterEngine
对象。通过调用 run()
预热引擎,然后使用 environment()
视图修饰符将此对象注入到 ContentView
中。
import SwiftUI
import Flutter
// 下面的库将插件与 iOS 平台代码连接到此应用程序。
import FlutterPluginRegistrant
@Observable
class FlutterDependencies {
let flutterEngine = FlutterEngine(name: "my flutter engine")
init() {
// 使用默认的 Flutter 路由运行默认的 Dart 入口点。
flutterEngine.run()
// 将插件与 iOS 平台代码连接到此应用程序。
GeneratedPluginRegistrant.register(with: self.flutterEngine);
}
}
@main
struct MyApp: App {
// flutterDependencies 将通过视图环境注入。
@State var flutterDependencies = FlutterDependencies()
var body: some Scene {
WindowGroup {
ContentView()
.environment(flutterDependencies)
}
}
}
例如,我们在应用程序委托的应用程序启动时演示创建 FlutterEngine
,并将其公开为属性。
import UIKit
import Flutter
// 下面的库将插件与 iOS 平台代码连接到此应用程序。
import FlutterPluginRegistrant
@UIApplicationMain
class AppDelegate: FlutterAppDelegate { // 关于 FlutterAppDelegate 的更多信息。
lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 使用默认的 Flutter 路由运行默认的 Dart 入口点。
flutterEngine.run();
// 将插件与 iOS 平台代码连接到此应用程序。
GeneratedPluginRegistrant.register(with: self.flutterEngine);
return super.application(application, didFinishLaunchingWithOptions: launchOptions);
}
}
以下示例演示了在应用程序委托的应用程序启动时创建 FlutterEngine
,并将其公开为属性。
@import UIKit;
@import Flutter;
@interface AppDelegate : FlutterAppDelegate // 关于 FlutterAppDelegate 的更多信息,请参见下文。
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
// 下面的库将插件与 iOS 平台代码连接到此应用程序。
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
#import "AppDelegate.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
// 使用默认的 Flutter 路由运行默认的 Dart 入口点。
[self.flutterEngine run];
// 将插件与 iOS 平台代码连接到此应用程序。
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
使用 FlutterEngine 显示 FlutterViewController
#以下示例显示了一个通用的 ContentView
,其中包含一个连接到 Flutter 屏幕的 NavigationLink
。首先,创建一个 FlutterViewControllerRepresentable
来表示 FlutterViewController
。FlutterViewController
构造函数将预热的 FlutterEngine
作为参数,该参数通过视图环境注入。
import SwiftUI
import Flutter
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
// Flutter 依赖项通过视图环境传入。
@Environment(FlutterDependencies.self) var flutterDependencies
func makeUIViewController(context: Context) -> some UIViewController {
return FlutterViewController(
engine: flutterDependencies.flutterEngine,
nibName: nil,
bundle: nil)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink("My Flutter Feature") {
FlutterViewControllerRepresentable()
}
}
}
}
现在,您已将 Flutter 屏幕嵌入到 iOS 应用中。
以下示例显示了一个通用的 ViewController
,其中包含一个连接到呈现 FlutterViewController
的 UIButton
。FlutterViewController
使用在 AppDelegate
中创建的 FlutterEngine
实例。
import UIKit
import Flutter
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 创建一个按钮,按下时调用 showFlutter 函数。
let button = UIButton(type:UIButton.ButtonType.custom)
button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
button.setTitle("Show Flutter!", for: UIControl.State.normal)
button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
button.backgroundColor = UIColor.blue
self.view.addSubview(button)
}
@objc func showFlutter() {
let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
let flutterViewController =
FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
present(flutterViewController, animated: true, completion: nil)
}
}
现在,您已将 Flutter 屏幕嵌入到 iOS 应用中。
以下示例显示了一个通用的 ViewController
,其中包含一个连接到呈现 FlutterViewController
的 UIButton
。FlutterViewController
使用在 AppDelegate
中创建的 FlutterEngine
实例。
@import Flutter;
#import "AppDelegate.h"
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建一个按钮,按下时调用 showFlutter 函数。
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self
action:@selector(showFlutter)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
button.backgroundColor = UIColor.blueColor;
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
}
- (void)showFlutter {
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
现在,您已将 Flutter 屏幕嵌入到 iOS 应用中。
或者 - 使用隐式 FlutterEngine 创建 FlutterViewController
#作为前面示例的替代方法,您可以让 FlutterViewController
隐式创建自己的 FlutterEngine
,而无需提前预热。
这通常不推荐,因为按需创建 FlutterEngine
可能会在呈现 FlutterViewController
和渲染其第一帧之间引入明显的延迟。但是,如果很少显示 Flutter 屏幕,没有好的启发式方法来确定何时应启动 Dart VM,并且 Flutter 不需要在视图控制器之间保持状态,这可能会很有用。
要让 FlutterViewController
在没有现有 FlutterEngine
的情况下呈现,请省略 FlutterEngine
的构造,并创建一个没有引擎引用的 FlutterViewController
。
import SwiftUI
import Flutter
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
return FlutterViewController(
project: nil,
nibName: nil,
bundle: nil)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink("My Flutter Feature") {
FlutterViewControllerRepresentable()
}
}
}
}
// 省略现有代码。
func showFlutter() {
let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
present(flutterViewController, animated: true, completion: nil)
}
// 省略现有代码。
- (void)showFlutter {
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
有关延迟和内存用量的更多探讨,请参阅加载顺序和性能。
使用 FlutterAppDelegate
#建议但不要求让应用程序的 UIApplicationDelegate
子类化 FlutterAppDelegate
。
FlutterAppDelegate
执行以下功能:
- 将应用程序回调(例如
openURL
)转发到插件,例如 local_auth。 - 保持 Flutter 连接打开
在调试模式下,当手机屏幕锁定是。
创建 FlutterAppDelegate 子类
#在 UIKit 应用中创建 FlutterAppDelegate
的子类已在启动 FlutterEngine 和 FlutterViewController 部分 中进行了展示。在 SwiftUI 应用中,您可以创建一个 FlutterAppDelegate
的子类,并使用 Observable()
宏对其进行注释,如下所示:
import SwiftUI
import Flutter
import FlutterPluginRegistrant
@Observable
class AppDelegate: FlutterAppDelegate {
let flutterEngine = FlutterEngine(name: "my flutter engine")
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 使用默认的 Flutter 路由运行默认的 Dart 入口点。
flutterEngine.run();
// 用于连接插件(只有当您拥有带有 iOS 平台代码的插件时)。
GeneratedPluginRegistrant.register(with: self.flutterEngine);
return true;
}
}
@main
struct MyApp: App {
// 使用此属性包装器告诉 SwiftUI
// 它应该为应用程序委托使用 AppDelegate 类
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
然后,在您的视图中,可以通过视图环境访问 AppDelegate
。
import SwiftUI
import Flutter
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
// 通过视图环境访问 AppDelegate。
@Environment(AppDelegate.self) var appDelegate
func makeUIViewController(context: Context) -> some UIViewController {
return FlutterViewController(
engine: appDelegate.flutterEngine,
nibName: nil,
bundle: nil)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink("My Flutter Feature") {
FlutterViewControllerRepresentable()
}
}
}
}
如果您无法直接将 FlutterAppDelegate 作为子类
#如果您的应用程序委托无法直接将 FlutterAppDelegate
作为子类,请使您的应用程序委托实现 FlutterAppLifeCycleProvider
协议,以确保您的插件收到必要的回调。否则,依赖于这些事件的插件可能会出现未定义的行为。
例如:
import Foundation
import Flutter
@Observable
class AppDelegate: UIResponder, UIApplicationDelegate, FlutterAppLifeCycleProvider {
private let lifecycleDelegate = FlutterPluginAppLifeCycleDelegate()
let flutterEngine = FlutterEngine(name: "my flutter engine")
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
flutterEngine.run()
return lifecycleDelegate.application(application, didFinishLaunchingWithOptions: launchOptions ?? [:])
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
lifecycleDelegate.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
lifecycleDelegate.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
lifecycleDelegate.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return lifecycleDelegate.application(app, open: url, options: options)
}
func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
return lifecycleDelegate.application(application, handleOpen: url)
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
return lifecycleDelegate.application(application, open: url, sourceApplication: sourceApplication ?? "", annotation: annotation)
}
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
lifecycleDelegate.application(application, performActionFor: shortcutItem, completionHandler: completionHandler)
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
lifecycleDelegate.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
lifecycleDelegate.application(application, performFetchWithCompletionHandler: completionHandler)
}
func add(_ delegate: FlutterApplicationLifeCycleDelegate) {
lifecycleDelegate.add(delegate)
}
}
@import Flutter;
@import UIKit;
@import FlutterPluginRegistrant;
@interface AppDelegate : UIResponder <UIApplicationDelegate, FlutterAppLifeCycleProvider>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
实现应该主要委托给 FlutterPluginAppLifeCycleDelegate
:
@interface AppDelegate ()
@property (nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;
@end
@implementation AppDelegate
- (instancetype)init {
if (self = [super init]) {
_lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
}
return self;
}
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id>*))launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
[self.flutterEngine runWithEntrypoint:nil];
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}
// 返回关键窗口的 rootViewController,如果它是 FlutterViewController。
// 否则,返回 nil。
- (FlutterViewController*)rootFlutterViewController {
UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
if ([viewController isKindOfClass:[FlutterViewController class]]) {
return (FlutterViewController*)viewController;
}
return nil;
}
- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
[_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}
- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
[_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[_lifeCycleDelegate application:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
return [_lifeCycleDelegate application:application openURL:url options:options];
}
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
return [_lifeCycleDelegate application:application handleOpenURL:url];
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
sourceApplication:(NSString*)sourceApplication
annotation:(id)annotation {
return [_lifeCycleDelegate application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
}
- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler {
[_lifeCycleDelegate application:application
performActionForShortcutItem:shortcutItem
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
completionHandler:(nonnull void (^)(void))completionHandler {
[_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}
- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
[_lifeCycleDelegate addDelegate:delegate];
}
@end
启动选项
#这些示例演示了如何使用默认启动设置运行 Flutter。
为了自定义 Flutter 运行时,您还可以指定 Dart 入口点、库和路由。
Dart 入口点
#默认情况下,在 FlutterEngine
上调用 run
会运行 lib/main.dart
文件的 main()
Dart 函数。
您还可以使用带有指定不同 Dart 函数的 NSString
的 runWithEntrypoint
来运行不同的入口点函数。
Dart 库
#除了指定 Dart 函数外,您还可以指定特定文件中的入口点函数。
例如,以下代码运行 lib/other_file.dart
中的 myOtherEntrypoint()
,而不是 lib/main.dart
中的 main()
:
flutterEngine.run(withEntrypoint: "myOtherEntrypoint", libraryURI: "other_file.dart")
[flutterEngine runWithEntrypoint:@"myOtherEntrypoint" libraryURI:@"other_file.dart"];
路由
#从 Flutter 1.22 版本开始,在构建 FlutterEngine 或 FlutterViewController 时,可以为您的 Flutter WidgetsApp
设置初始路由。
let flutterEngine = FlutterEngine()
// FlutterDefaultDartEntrypoint 与 nil 相同,它将运行 main()。
engine.run(
withEntrypoint: "main", initialRoute: "/onboarding")
FlutterEngine *flutterEngine = [[FlutterEngine alloc] init];
// FlutterDefaultDartEntrypoint 与 nil 相同,它将运行 main()。
[flutterEngine runWithEntrypoint:FlutterDefaultDartEntrypoint
initialRoute:@"/onboarding"];
这段代码将您的 dart:ui
的 PlatformDispatcher.defaultRouteName
设置为 "/onboarding"
,而不是 "/"
。
或者,要直接构建 FlutterViewController 而无需预热 FlutterEngine:
let flutterViewController = FlutterViewController(
project: nil, initialRoute: "/onboarding", nibName: nil, bundle: nil)
FlutterViewController* flutterViewController =
[[FlutterViewController alloc] initWithProject:nil
initialRoute:@"/onboarding"
nibName:nil
bundle:nil];
有关 Flutter 路由的更多信息,请参阅导航和路由。
其他
#前面的示例仅说明了自定义 Flutter 实例初始化方式的几种方法。使用平台通道,您可以自由地推送数据或以任何您想要的方式准备 Flutter 环境,然后使用 FlutterViewController
显示 Flutter UI。
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。