
一、UIScene 是什么?
在 iOS 13 发布时,Apple 引入了 UIScene ,正式把一个 App 的生命周期从「单个进程」拆成了「多个场景(Scene)」。
简单理解:
| 旧时代(AppDelegate) | 新时代(UIScene) |
|---|---|
| 一个 App 只有一个窗口 | 一个 App 可以同时有多个窗口(iPad 分屏、外接屏、多任务) |
| 所有 UI 生命周期都在 AppDelegate | 每个窗口(Scene)都有自己的 SceneDelegate 管理生命周期 |
applicationDidBecomeActive 等方法全局生效 |
每个 Scene 独立触发 sceneDidBecomeActive 等 |
二、2025 年最硬的刀:iOS 26 强制适配 UIScene 警告
未进行适配的iOS项目,运行后会有一个红色的警告: 
三、Flutter 项目 UIScene 迁移步骤
前置条件
yaml
# pubspec.yaml
environment:
sdk: ^3.10.0
flutter: ">=3.38.0"
方式一:自动迁移(推荐先试)
bash
flutter config --enable-uiscene-migration
flutter clean
flutter run
看到 Finished migration to UIScene lifecycle 就说明成功了,直接跳到最后测试即可。如果有自定义代码,可能会提示手动迁移。
方式二:手动迁移(自定义过 AppDelegate 的项目)
1. 改造 AppDelegate
- Swift
swift
import UIKit
import Flutter
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 不要再在这里注册插件了!
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// 所有插件注册必须搬到这里
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
// 你原来的 MethodChannel、PlatformView、原生代码全部搬到这里
let channel = FlutterMethodChannel(
name: "your_channel",
binaryMessenger: engineBridge.applicationRegistrar.messenger()
)
}
}
2. 添加 Application Scene Manifest
打开 ios/Runner/Info.plist,加入以下内容:
plist
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/> <!-- 普通 App 保持 false -->
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key><string>UIWindowScene</string>
<key>UISceneDelegateClassName</key><string>FlutterSceneDelegate</string>
<key>UISceneConfigurationName</key><string>flutter</string>
<key>UISceneStoryboardFile</key><string>Main</string>
</dict>
</array>
</dict>
</dict>
3. (可选)创建自定义 SceneDelegate
如果你原来在 AppDelegate 里写了前后台逻辑,全部搬到这里:
swift
// SceneDelegate.swift
import UIKit
import Flutter
class SceneDelegate: FlutterSceneDelegate {
override func sceneDidBecomeActive(_ scene: UIScene) {
super.sceneDidBecomeActive(scene)
// 原来 applicationDidBecomeActive 的代码
}
override func sceneWillResignActive(_ scene: UIScene) {
super.sceneWillResignActive(scene)
// 原来 applicationWillResignActive 的代码
}
}
然后把 Info.plist 里的 UISceneDelegateClassName 改成:
plist
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
4. 清理重建
bash
flutter clean
cd ios && pod install --repo-update && cd ..
flutter run
四、Add-to-App (ATA) 项目迁移
如果你的 Flutter 是嵌入现有 iOS App(Add-to-App),迁移更复杂,因为需要手动管理 FlutterEngine 和场景生命周期。以下是详细步骤(基于 Flutter 官方指南):
1. 在 application:configurationForConnecting:options: 中设置 Delegate Class
在你的现有 AppDelegate 中,为连接的场景配置 FlutterSceneDelegate。这确保每个新场景使用 Flutter 的场景委托。
Swift 示例:
swift
override func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let configuration = UISceneConfiguration(
name: nil,
sessionRole: connectingSceneSession.role
)
configuration.delegateClass = FlutterSceneDelegate.self // 设置为 FlutterSceneDelegate
return configuration
}
Objective-C 示例:
OC
- (UISceneConfiguration *)application:(UIApplication *)application
configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
options:(UISceneConnectionOptions *)options {
UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:nil sessionRole:connectingSceneSession.role];
configuration.delegateClass = [FlutterSceneDelegate class];
return configuration;
}
2. 禁用多场景支持(除非必要)
在 Info.plist 中设置 UIApplicationSupportsMultipleScenes 为 NO,避免不必要的多窗口复杂性。除非你的 App 明确需要 iPad 分屏或外部显示器支持。
plist
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
3. 手动创建并运行 FlutterEngine
在 SceneDelegate 的 scene:willConnectToSession:options: 中手动创建 FlutterEngine,运行它,注册插件,并设置视图层次。Flutter 不会自动处理引擎创建。
Swift 示例(子类 FlutterSceneDelegate):
swift
class SceneDelegate: FlutterSceneDelegate {
let flutterEngine = FlutterEngine(name: "my flutter engine") // 手动创建引擎
override func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else { return }
window = UIWindow(windowScene: windowScene)
flutterEngine.run() // 运行引擎
GeneratedPluginRegistrant.register(with: flutterEngine) // 注册插件
self.registerSceneLifeCycle(with: flutterEngine) // 注册场景生命周期
let viewController = ViewController(engine: flutterEngine) // 创建 FlutterViewController
window?.rootViewController = viewController
window?.makeKeyAndVisible()
super.scene(scene, willConnectTo: session, options: connectionOptions)
}
}
Objective-C 示例:
OC
@interface SceneDelegate : FlutterSceneDelegate
@property (nonatomic, strong) FlutterEngine *flutterEngine;
@end
@implementation SceneDelegate
- (instancetype)init {
if (self = [super init]) {
_flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
}
return self;
}
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions {
if (![scene isKindOfClass:[UIWindowScene class]]) {
return;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
[self.flutterEngine run];
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
[self registerSceneLifeCycleWithFlutterEngine:self.flutterEngine];
ViewController *viewController = [[ViewController alloc] initWithEngine:self.flutterEngine];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
[super scene:scene willConnectToSession:session options:connectionOptions];
}
@end
4. 注册和注销场景生命周期
- 注册(registerSceneLifeCycle) :在
willConnectToSession中调用,确保引擎接收场景回调。 - 注销(unregisterSceneLifeCycle) :在场景断开(如
sceneDidDisconnect)或切换时调用,避免内存泄漏。
Swift 示例:
swift
// 注册
self.registerSceneLifeCycle(with: flutterEngine)
// 注销(例如在 sceneDidDisconnect 中)
self.unregisterSceneLifeCycle(with: flutterEngine)
Objective-C 示例:
OC
// 注册
[self registerSceneLifeCycleWithFlutterEngine:self.flutterEngine];
// 注销
[self unregisterSceneLifeCycleWithFlutterEngine:self.flutterEngine];
5. 替代方案:实现 FlutterSceneLifeCycleProvider
如果无法直接子类 FlutterSceneDelegate,实现 FlutterSceneLifeCycleProvider 协议,手动转发回调。
Swift 示例:
swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate, FlutterSceneLifeCycleProvider {
var sceneLifeCycleDelegate: FlutterPluginSceneLifeCycleDelegate =
FlutterPluginSceneLifeCycleDelegate()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options: UIScene.ConnectionOptions) {
sceneLifeCycleDelegate.scene(scene, willConnectTo: session, options: options)
// 其他设置...
}
// 实现其他场景回调,如 sceneDidBecomeActive 等
}
6. 多场景支持
如果启用多场景,为每个场景单独创建 FlutterEngine,并在连接/断开时注册/注销。忘记注销可能导致重复回调或泄漏。
参考 Apple 指南:Migrating to the UIKit Scene-Based Life Cycle。
五、Flutter 插件迁移
如果你的插件依赖 UI 生命周期(如权限请求、前后台切换),必须适配场景委托。并非所有插件都需要改;仅那些使用 UIApplicationDelegate UI 事件的才需更新。以下是详细步骤:
1. 更新 pubspec.yaml
确保插件支持新版本。
yaml
environment:
sdk: ^3.10.0
flutter: ">=3.38.0"
2. 采用 FlutterSceneLifeCycleDelegate
更新插件主类,实现协议以接收场景回调。
Swift 示例:
swift
public final class MyPlugin: NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate {
// 插件逻辑...
}
Objective-C 示例:
OC
@interface MyPlugin : NSObject <FlutterPlugin, FlutterSceneLifeCycleDelegate>
// 插件逻辑...
@end
3. 注册插件为场景委托接收者
在 register(with:) 中,同时注册为 AppDelegate 和 SceneDelegate,支持向后兼容。
Swift 示例:
swift
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = MyPlugin()
registrar.addApplicationDelegate(instance) // 保持 AppDelegate 支持
registrar.addSceneDelegate(instance) // 添加 SceneDelegate 支持
// 其他注册,如 MethodChannel...
}
Objective-C 示例:
OC
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
MyPlugin *instance = [[MyPlugin alloc] init];
[registrar addApplicationDelegate:instance];
[registrar addSceneDelegate:instance];
// 其他注册...
}
4. 实现场景生命周期回调
实现协议方法,替换旧的 AppDelegate UI 事件。每个方法对应一个场景事件。
Swift 示例(关键方法):
swift
// 场景连接(处理启动选项)
public func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions?
) -> Bool {
// 从 connectionOptions 处理启动 URL、UserActivity 等
// 原来在 didFinishLaunchingWithOptions 的逻辑搬到这里
return true
}
// 其他回调
public func sceneDidDisconnect(_ scene: UIScene) { /* 清理资源 */ }
public func sceneWillEnterForeground(_ scene: UIScene) { /* 前台准备 */ }
public func sceneDidBecomeActive(_ scene: UIScene) { /* 激活逻辑 */ }
public func sceneWillResignActive(_ scene: UIScene) { /* 暂停逻辑 */ }
public func sceneDidEnterBackground(_ scene: UIScene) { /* 后台处理 */ }
public func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) -> Bool { /* URL 处理 */ }
public func scene(_ scene: UIScene, continue userActivity: NSUserActivity) -> Bool { /* 续接活动 */ }
public func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) -> Bool { /* 快捷方式 */ }
Objective-C 示例(关键方法):
OC
- (BOOL)scene:(UIScene*)scene willConnectToSession:(UISceneSession*)session options:(nullable UISceneConnectionOptions*)connectionOptions {
// 处理启动选项
return YES;
}
// 其他回调
- (void)sceneDidDisconnect:(UIScene*)scene { }
- (void)sceneWillEnterForeground:(UIScene*)scene { }
- (void)sceneDidBecomeActive:(UIScene*)scene { }
- (void)sceneWillResignActive:(UIScene*)scene { }
- (void)sceneDidEnterBackground:(UIScene*)scene { }
- (BOOL)scene:(UIScene*)scene openURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts { return YES; }
- (BOOL)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity { return YES; }
- (BOOL)windowScene:(UIWindowScene*)windowScene performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL))completionHandler { return YES; }
5. 迁移启动逻辑
将从 application:didFinishLaunchingWithOptions: 的逻辑移到 scene:willConnectToSession:options:,因为 UIScene 后 launchOptions 可能为 nil。
6. 测试与发布
- 测试所有 UI 事件(如推送、权限)。
- 保持 AppDelegate 注册以支持旧 App。
六、最后
做完以上,你的 Flutter iOS 项目就彻底拥抱了 UIScene 了。