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

然后将它设置为window的 rootViewController:

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 (原生功能集成)

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

  • AppDelegate → Application services (App服务)
  • SceneDelegate → UI lifecycle (UI生命周期)

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

相关推荐
大龄秃头程序员几秒前
Flutter 中我为什么把 GetX 定位为高效率方案,而不是默认架构方案
flutter
Anhty2 小时前
2026三款AI变声器测评:iOS男变女自然声线实测
人工智能·功能测试·ios·智能手机
传奇开心果编程2 小时前
组合优先开发范式:从汉字造字原理到声明式UI组件组合思想
android·flutter·ios·swiftui·移动开发·harmonyos·android jetpack
腾讯端服务3 小时前
一码Duo端-Kuikly-iOS折叠屏新UI适配实践
android·ui·ios·kotlin·鸿蒙
2501_915918413 小时前
Windows 上开发 React Native iOS,真机调试、Fast Refresh 与打包 ipa
windows·vscode·react native·ios·个人开发·swift·敏捷流程
2501_9445215912 小时前
Ramda 跨端三方库仓颉适配全流程
开发语言·javascript·flutter·php
aiopencode16 小时前
局域网设备抓包怎么做?用 TraceEagle 抓电视、游戏机和 IoT 流量
后端·ios
奇牙coding18 小时前
GPT-5.5 报 401 invalid_api_key 怎么办?换密钥前按 4 层排查请求头、项目权限和端点
数据库·gpt·ios·ai
szp200519 小时前
中文字幕烧进视频后整片空白:CoreText 低报行高,导出照样成功
ios·swift
大龄秃头程序员19 小时前
Flutter 架构里总被混淆的 IoC、DI、Service Locator,到底是什么关系?
flutter