Flutter iOS UISceneDelegate 迁移指南:理清新的 Scene 生命周期

引言

如果你最近升级了 Flutter 项目或 Xcode,可能已经遇到了与 UISceneDelegate 相关的警告或构建问题。

Apple 在 iOS 13 中引入了基于 Scene 的生命周期。较新的 Xcode 版本也在推动开发者使用 UISceneDelegate,而不是继续在 AppDelegate 中直接管理应用窗口。

对 Flutter 开发者来说,这次迁移可能有点让人摸不着头脑,因为老项目通常把所有初始化逻辑都放在这个文件里:

复制代码
ios/Runner/AppDelegate.swift

而采用新生命周期的 iOS 应用,需要调整这套结构。

这篇文章会介绍:

  • 为什么需要 UISceneDelegate
  • Flutter iOS 项目的结构有哪些变化
  • 如何完成迁移
  • 如何让 FlutterEngine 继续正常工作
  • 常见问题及处理方法

UISceneDelegate 是什么?

在 iOS 13 之前,iOS 应用的生命周期主要由一个对象管理:

objectivec 复制代码
UIApplicationDelegate

应用窗口也在这个文件中创建:

复制代码
AppDelegate.swift

例如:

swift 复制代码
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()

到了 iOS 13,Apple 引入了 多个 Scene 的支持。

一个 Scene 可以理解为应用界面的一个独立实例,比如:

  • iPad 上的多个窗口
  • 外接显示器上的界面
  • 不同的应用会话

与这些界面实例相关的生命周期管理,也就从:

swift 复制代码
UIApplicationDelegate

转移到了:

swift 复制代码
UISceneDelegate

为什么 Flutter 开发者会遇到这个问题?

老的 Flutter 项目通常包含以下文件:

css 复制代码
ios/
 └── Runner/
      ├── AppDelegate.swift.swift
      ├── Info.plist
      └── Main.storyboard

AppDelegate 一般是这样写的:

swift 复制代码
import UIKit
import Flutter

@main
class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions:
        [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(
            with: self
        )
        return super.application(
            application,
            didFinishLaunchingWithOptions: launchOptions
        )
    }
}

这种写法能正常工作,是因为 Flutter 会自动创建根 FlutterViewController。

但使用 UISceneDelegate 后,窗口的生命周期管理方式就变了。

引入 UISceneDelegate 后,Flutter iOS 项目怎么组织?

推荐的结构如下:

markdown 复制代码
ios/
 └── Runner/Runner/
      ├── AppDelegate.swift
      ├── SceneDelegate.swift
      ├── Info.plist
      └── Runner.xcodeproj

两者的职责也需要分开。

AppDelegate

主要负责:

diff 复制代码
- Firebase initialization (Firebase 初始化)
- Plugin registration (插件注册)
- Background services  (后台服务)     
- Application-level events (应用级别的事件)

例如:

swift 复制代码
import UIKit
import Flutter

@main
class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions:
        [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(
            with: self
        )
        return super.application(
            application,
            didFinishLaunchingWithOptions: launchOptions
        )
    }
}

SceneDelegate

主要负责:

  • 创建 UIWindow
  • FlutterViewController 设置到窗口上
  • 管理 Scene 的生命周期

例如:

swift 复制代码
import UIKit
import Flutter

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    func scene(
        _ scene: UIScene,
        willConnectTo session: UISceneSession,
        options connectionOptions:
        UIScene.ConnectionOptions
    ) {
        guard let windowScene =
                scene as? UIWindowScene else {
            return
        }
        let flutterViewController =
            FlutterViewController(
                engine: nil,
                nibName: nil,
                bundle: nil
            )
        let window =
            UIWindow(windowScene: windowScene)
        window.rootViewController =
            flutterViewController
        self.window = window
        window.makeKeyAndVisible()
    }
}

FlutterEngine:迁移时别忽略这一点

一个常见的坑是:直接在 SceneDelegate 中新建 FlutterViewController,却没有考虑 FlutterEngine 的管理和复用。

如果你的应用用到了以下功能,就应该复用已有的 FlutterEngine

  • CallKit
  • Background services (后台服务)
  • MethodChannels (方法通道)
  • Push notifications (推送通知)

这里可以复用 FlutterEngine,例如:

swift 复制代码
let appDelegate = UIApplication.shared.delegate as! AppDelegate

let flutterViewController = appDelegate.window?.rootViewController as? FlutterViewController

然后将它设置为windowrootViewController:

swift 复制代码
window.rootViewController = flutterViewController

更新 Info.plist

你还需要在 Info.plist 中注册 Scene 配置,加入以下内容:

xml 复制代码
<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <false/>
    <key>UISceneConfigurations</key>
    <dict>
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneConfigurationName</key>
                <string>Default Configuration</string>
                <key>UISceneDelegateClassName</key>
                <string>
                $(PRODUCT_MODULE_NAME).SceneDelegate
                </string>
            </dict>
        </array>
    </dict>
</dict>

迁移时容易踩的坑

1. UIWindow 相关逻辑仍然留在 AppDelegate 中

旧写法:

swift 复制代码
self.window = UIWindow()

迁移后,应把创建 UIWindow 的相关逻辑移到:

复制代码
SceneDelegate.swift

2. 创建了多个 FlutterEngine

错误示例:

swift 复制代码
FlutterEngine()
FlutterEngine()
FlutterEngine()

这可能导致以下功能出问题:

  • plugins (插件)
  • navigation (页面导航)
  • MethodChannels (方法通道)

所以尽量复用同一个 FlutterEngine

3. 漏掉 GeneratedPluginRegistrant

如果少了这段注册代码:

swift 复制代码
GeneratedPluginRegistrant.register(
  with:self
)

相关插件 plugins 可能无法正常工作,比如:

  • Firebase
  • Camera
  • Notifications

4. 忘记配置 Info.plist

即使已经创建了 SceneDelegate,如果没有配置 Info.plist,iOS 也不会加载它。

5. 项目中集成了 Firebase、Agora 或 CallKit

对于集成了这些功能、业务比较复杂的 Flutter 应用,可以按下面的思路划分职责:

yaml 复制代码
Flutter App
      |
      |
AppDelegate
      |
      |
Firebase
CallKit
Push Notifications

界面UI相关的部分则放在另一侧:

markdown 复制代码
SceneDelegate
      |
      |
UIWindow
FlutterViewController

记住这个分工:

  • Services (服务相关逻辑)AppDelegate
  • UI lifecycle (UI生命周期)SceneDelegate

迁移后怎么验证?

迁移完成后,运行:

bash 复制代码
flutter clean

cd ios
pod deintegrate
pod install
cd ..
flutter run

然后逐项检查:

  • App launches (应用能否正常启动)
  • Navigation (页面导航是否正常)
  • Firebase 是否成功初始化(如果使用了 Firebase)
  • Push notifications (推送通知是否正常)
  • Background services (后台服务是否正常)
  • MethodChannels 是否能正常响应

最后

UISceneDelegate 迁移并不是 Flutter 独有的问题,而是为了适配 Apple 新的应用生命周期架构。

对于功能简单的 Flutter 应用,迁移工作量通常不大。

但如果是正式上线的应用,并且用到了以下功能:

  • Firebase
  • CallKit
  • Background execution (后台运行)
  • Native integrations (原生功能集成)

就需要仔细梳理职责,把这两部分分清楚:

  • AppDelegateApplication services (App服务)
  • SceneDelegateUI lifecycle (UI生命周期)

按这个思路,完成这层职责划分后,Flutter 应用就能适配较新的 Xcode 版本,也为后续 iOS 版本的适配做好准备。

相关推荐
00后程序员张1 小时前
Xcode vs KXApp,体积差几十G,选轻量方案还是完整工具链?
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
技术任我行XTing4 小时前
【DFX系列】Flutter 鸿蒙应用外接纹理介绍及问题定位
flutter·harmonyos
SXkehuirongsheng4 小时前
APP 定制开发哪家交付质量好?
app
恋猫de小郭6 小时前
Flutter GSoC 2026 提案进度解读,补上 DevTools、FFI 和原生平台的关键缺口
android·前端·flutter
2501_915909066 小时前
怎么用 FlutterFlow 把应用发布到 App Store?
android·ios·小程序·https·uni-app·iphone·webview
2501_916008891 天前
全平台抓包工具,Windows、iPhone、Linux三个平台抓包测试
网络协议·计算机网络·网络安全·ios·adb·https·udp
2501_915918411 天前
怎么把 Python 写的 Flet 应用打包成 iOS App 并上架 App Store?
android·ios·小程序·https·uni-app·iphone·webview
●VON1 天前
Flutter 鸿蒙插件适配实战:用 flutter_native_timezone_2025 1.0.1 读取当前时区与系统目录
flutter·华为·harmonyos·鸿蒙