在 iOS 应用中添加 Flutter 页面
了解如何在现有 iOS 应用中添加单个 Flutter 页面。
本指南介绍如何在现有 iOS 应用中添加单个 Flutter 页面。
启动 FlutterEngine 和 FlutterViewController
#
若要从现有 iOS 应用启动 Flutter 页面,请启动 FlutterEngine
和 FlutterViewController。
FlutterEngine 的生命周期可能与 FlutterViewController 相同,也可能比 FlutterViewController 更长。
请参阅 加载顺序和性能,了解更多关于预热引擎在延迟和内存方面取舍的分析。
创建一个 FlutterEngine
#在哪里创建 FlutterEngine 取决于你的宿主应用。
在这个例子中,我们会在一个名为 FlutterDependencies 的
SwiftUI Observable
对象中创建 FlutterEngine 对象。调用 run() 预热引擎,然后使用 environment() 视图修饰符将此对象注入 ContentView。
import SwiftUI
import Flutter
// The following library connects plugins with iOS platform code to this app.
import FlutterPluginRegistrant
@Observable
class FlutterDependencies {
let flutterEngine = FlutterEngine(name: "my flutter engine")
init() {
// Runs the default Dart entrypoint with a default Flutter route.
flutterEngine.run()
// Connects plugins with iOS platform code to this app.
GeneratedPluginRegistrant.register(with: self.flutterEngine);
}
}
@main
struct MyApp: App {
// flutterDependencies will be injected through the view environment.
@State var flutterDependencies = FlutterDependencies()
var body: some Scene {
WindowGroup {
ContentView()
.environment(flutterDependencies)
}
}
}
在这个例子中,我们会在应用启动时,在 app delegate 中创建一个
FlutterEngine,并将其作为属性暴露。
import UIKit
import Flutter
// The following library connects plugins with iOS platform code to this app.
import FlutterPluginRegistrant
@UIApplicationMain
class AppDelegate: FlutterAppDelegate { // More on the FlutterAppDelegate.
lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Runs the default Dart entrypoint with a default Flutter route.
flutterEngine.run();
// Connects plugins with iOS platform code to this app.
GeneratedPluginRegistrant.register(with: self.flutterEngine);
return super.application(application, didFinishLaunchingWithOptions: launchOptions);
}
}
下面的示例演示了如何在应用启动时,在 app delegate 中创建一个
FlutterEngine,并将其作为属性暴露。
@import UIKit;
@import Flutter;
@interface AppDelegate : FlutterAppDelegate // More on the FlutterAppDelegate below.
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
// The following library connects plugins with iOS platform code to this app.
#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"];
// Runs the default Dart entrypoint with a default Flutter route.
[self.flutterEngine run];
// Connects plugins with iOS platform code to this app.
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
使用 FlutterEngine 展示 FlutterViewController
#
下面的示例展示了一个通用的 ContentView,它通过 NavigationLink
连接到 Flutter 页面。首先,创建一个 FlutterViewControllerRepresentable 来表示
FlutterViewController。FlutterViewController 构造函数接收预热好的
FlutterEngine 作为参数,该参数通过视图环境 (view environment) 注入。
import SwiftUI
import Flutter
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
// Flutter dependencies are passed in through the view environment.
@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()
}
}
}
}
现在,你的 iOS 应用中已经嵌入了一个 Flutter 页面。
下面的示例展示了一个通用的 ViewController,其中有一个 UIButton 用于呈现 FlutterViewController。这个 FlutterViewController 会使用在 AppDelegate 中创建的
FlutterEngine 实例。
import Flutter
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Make a button to call the showFlutter function when pressed.
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)
}
}
现在,你的 iOS 应用中已经嵌入了一个 Flutter 页面。
下面的示例展示了一个通用的 ViewController,其中有一个 UIButton 用于呈现 FlutterViewController。这个 FlutterViewController 会使用在 AppDelegate 中创建的
FlutterEngine 实例。
@import Flutter;
#import "AppDelegate.h"
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Make a button to call the showFlutter function when pressed.
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
现在,你的 iOS 应用中已经嵌入了一个 Flutter 页面。
或者 —— 使用隐式 FlutterEngine 创建 FlutterViewController
#
作为前一个示例的替代方案,你可以让 FlutterViewController
隐式创建自己的 FlutterEngine,而不提前预热引擎。
通常不建议这样做,因为按需创建 FlutterEngine 可能会在
FlutterViewController 呈现后、渲染第一帧前引入明显延迟。不过,如果 Flutter 页面很少显示、没有合适的启发式方法判断何时启动
Dart VM,且 Flutter 不需要在多个 view controller 之间保持状态,这种方式也可能有用。
若要在没有现有 FlutterEngine 的情况下呈现 FlutterViewController,请省略 FlutterEngine 的构造步骤,并在创建 FlutterViewController 时不要传入 engine 引用。
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()
}
}
}
}
// Existing code omitted.
func showFlutter() {
let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
present(flutterViewController, animated: true, completion: nil)
}
// Existing code omitted.
- (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 子类
#
启动 FlutterEngine 和 FlutterViewController
一节展示了如何在 UIKit 应用中创建 FlutterAppDelegate 子类。在 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 {
// Runs the default Dart entrypoint with a default Flutter route.
flutterEngine.run();
// Used to connect plugins (only if you have plugins with iOS platform code).
GeneratedPluginRegistrant.register(with: self.flutterEngine);
return true;
}
}
@main
struct MyApp: App {
// Use this property wrapper to tell SwiftUI
// it should use the AppDelegate class for the application delegate
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
然后,你可以在视图中通过视图环境 (view environment) 访问 AppDelegate。
import SwiftUI
import Flutter
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
// Access the AppDelegate through the view environment.
@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 成为子类
#
如果你的 app delegate 不能直接继承 FlutterAppDelegate,请让 app delegate 实现 FlutterAppLifeCycleProvider 协议,以确保 Flutter 插件能收到必要的回调。否则,依赖这些事件的插件可能会出现未定义行为。
例如:
import Foundation
import Flutter
@Observable
class AppDelegate: UIResponder, UIApplicationDelegate, FlutterAppLifeCycleProvider {
private let lifecycleDelegate = FlutterPluginAppLifeCycleDelegate()
let flutterEngine = FlutterEngine(name: "my flutter engine")
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];
}
// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns 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 入口点、library 和路由。
Dart 入口
#
默认情况下,在 FlutterEngine 上调用 run 会运行
lib/main.dart 文件中的 Dart main() 函数。
你也可以使用 runWithEntrypoint
并传入指定其他 Dart 函数的
NSString,以运行不同的入口点函数。
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 WidgetsApp
设置初始路由。
let flutterEngine = FlutterEngine()
// FlutterDefaultDartEntrypoint is the same as nil, which will run main().
engine.run(
withEntrypoint: "main", initialRoute: "/onboarding")
FlutterEngine *flutterEngine = [[FlutterEngine alloc] init];
// FlutterDefaultDartEntrypoint is the same as nil, which will run main().
[flutterEngine runWithEntrypoint:FlutterDefaultDartEntrypoint
initialRoute:@"/onboarding"];
这段代码会将你的 dart:ui 的 PlatformDispatcher.defaultRouteName
设置为 "/onboarding",而不是 "/"。
或者,也可以不预热 FlutterEngine,直接构造 FlutterViewController:
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 实例初始化方式。借助 平台通道,你可以在使用 FlutterViewController
呈现 Flutter UI 之前,以任何想要的方式推送数据或准备 Flutter 环境。
内容自适应尺寸的视图
#在 iOS 上,你也可以将嵌入的 FlutterView 设置为根据其内容自行调整尺寸。
let flutterViewController = FlutterViewController(engine: engine, nibName: nil, bundle: nil)
flutterViewController.isAutoResizable = true
_flutterViewController = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
_flutterViewController.autoResizable = YES;
限制
#
若要使用此功能,你的根 widget 必须支持无界约束。请避免在 widget 树顶层使用需要有界约束的 widget
(例如 ListView 或 LayoutBuilder),因为它们可能会与动态尺寸逻辑冲突。
实际上,这意味着相当多常见的 widget 并不受支持,例如 ScaffoldBuilder、CupertinoTimerPicker,或者任何内部依赖 LayoutBuilder 的 widget。如果不确定,可以使用 UnconstrainedBox 测试某个 widget
是否适用于内容自适应尺寸的视图,如下例所示:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context)
=> MaterialApp(home: MyPage());
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: UnconstrainedBox(
// TODO: Edit this line to check if a widget
// can cause problems with content-sized views.
child: Text('This works!'),
// child: Column(children: [Column(children: [Expanded(child: Text('This blows up!'))])]),
// child: ListView(children: [Text('This blows up!')]),
)
);
}
}
可运行的示例请参阅此 示例项目。
除非另有说明,本文档之所提及适用于 Flutter 3.44.0 版本。本页面最后更新时间:2026-06-11。查看文档源码 或者 为本页面内容提出建议。