概述

#

Apple 现在要求 iOS 开发者适配 UISceneDelegate 协议,这会改变应用程序启动时的初始化顺序。如果您的 iOS 应用修改了 application:didFinishLaunchingWithOptions:,则可能需要更新。

背景

#

大多数 Flutter 应用在 application:didFinishLaunchingWithOptions: 中不会有自定义逻辑。这些应用无需进行任何代码迁移。在大多数情况下,Flutter 会自动迁移 Info.plist

Apple 现在要求适配 UISceneDelegate,这会重新排列 iOS 应用的初始化顺序。指定 UISceneDelegate 后,Storyboard 的初始化会延迟到调用 application:didFinishLaunchingWithOptions: 之后。这意味着 UIApplicationDelegate.windowUIApplicationDelegate.window.rootViewController 无法从 application:didFinishLaunchingWithOptions: 访问。

Apple 正在推动 UISceneDelegate API 的适配,因为它允许应用程序拥有其 UI 的多个实例,例如在 iPadOS 上的多任务处理。

之前,Flutter 的文档指出 application:didFinishLaunchingWithOptions: 是设置平台通道以在宿主应用程序和 Flutter 之间创建互操作性的好地方。现在这不再是注册这些平台通道的可靠位置,因为 Flutter 引擎尚未创建。

迁移指南

#

Info.plist 迁移

#

UISceneDelegate 必须在应用的 Info.plistapplication:configurationForConnectingSceneSession:options: 中指定。如果未指定 UISceneDelegate,Flutter 工具会尝试自动编辑 Info.plist,因此除了再次运行 flutter runflutter build 之外,可能不需要任何操作。项目可以通过将以下内容添加到 Info.plist 进行手动升级。FlutterSceneDelegate 是 Flutter 框架中作为 UISceneDelegate 的新类。

Info.plist
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
 <key>UIApplicationSceneManifest</key>
 <dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <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>
</dict>

在 Xcode 编辑器中显示

Xcode plist editor for UISceneDelegate

application:didFinishLaunchingWithOptions: 中创建平台通道

#

以编程方式创建 FlutterViewController 的应用可以像以前一样继续运行。依赖 Storyboard(和 XIB)在 application:didFinishLaunchingWithOptions: 中创建平台通道的应用,现在应该使用 FlutterPluginRegistrant API 来实现相同的功能。

之前

#
swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
                                              binaryMessenger: controller.binaryMessenger)
    batteryChannel.setMethodCallHandler({
      [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
      // This method is invoked on the UI thread.
      // Handle battery messages.
    })

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
objc
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

  FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
                                          methodChannelWithName:@"samples.flutter.dev/battery"
                                          binaryMessenger:controller.binaryMessenger];

  [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    // This method is invoked on the UI thread.
    // TODO
  }];

  [GeneratedPluginRegistrant registerWithRegistry:self];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

之后

#
swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, FlutterPluginRegistrant {
  override func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    pluginRegistrant = self
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  func register(with registry: any FlutterPluginRegistry) {
    let registrar = registry.registrar(forPlugin: "battery")
    let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
                                              binaryMessenger: registrar!.messenger())
    batteryChannel.setMethodCallHandler({
      [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
      // This method is invoked on the UI thread.
      // Handle battery messages.
    })

    GeneratedPluginRegistrant.register(with: registry)
  }
}
objc
@interface AppDelegate () <flutterpluginregistrant>
@end

@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  self.pluginRegistrant = self;
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)registerWithRegistry:(NSObject<flutterpluginregistry>*)registry {
  NSObject<flutterpluginregistrar>* registrar = [registry registrarForPlugin:@"battery"];
  FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
                                          methodChannelWithName:@"samples.flutter.dev/battery"
                                          binaryMessenger:registrar.messenger];

  [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    // This method is invoked on the UI thread.
    // TODO
  }];

  [GeneratedPluginRegistrant registerWithRegistry:registry];
}
@end

通过 FlutterAppDelegate 以编程方式设置 FlutterPluginRegistrant

application:didFinishLaunchingWithOptions: 中注册插件

#

大多数传统 Flutter 项目在应用启动时使用 GeneratedPluginRegistrant 注册插件。GeneratedPluginRegistrant 对象在底层注册平台通道,应随着平台通道迁移而进行迁移。这将避免任何关于使用 FlutterLaunchEngine 的运行时警告。

swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, FlutterPluginRegistrant {
  override func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    pluginRegistrant = self
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  func register(with registry: any FlutterPluginRegistry) {
    GeneratedPluginRegistrant.register(with: registry)
  }
}
objc
@interface AppDelegate () <flutterpluginregistrant>
@end

@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  self.pluginRegistrant = self;
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)registerWithRegistry:(NSObject<flutterpluginregistry>*)registry {
  [GeneratedPluginRegistrant registerWithRegistry:registry];
}
@end

定制化 FlutterViewController 用法

#

对于由于创建平台通道以外的原因,在 application:didFinishLaunchingWithOptions: 中使用从 Storyboard 实例化 FlutterViewController 的应用,它们有责任适应新的初始化顺序。

迁移选项

  • 子类化 FlutterViewController 并将逻辑放入子类的 awakeFromNib 中。
  • Info.plistUIApplicationDelegate 中指定 UISceneDelegate,并将逻辑放入 scene:willConnectToSession:options: 中。更多信息请查看 Apple 的文档

示例

#
swift
@objc class MyViewController: FlutterViewController {
  override func awakeFromNib() {
    self.awakeFromNib()
    doSomethingWithFlutterViewController(self)
  }
}

时间线

#
  • 已合并到 main 分支:待定
  • 已合并到 stable 分支:待定
  • 未知:Apple 将其警告更改为断言,并且未适配 UISceneDelegate 的 Flutter 应用将在使用最新 SDK 启动时开始崩溃。

参考资料

#