概述

#

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 的應用程式可以像以前一樣繼續執行。依賴 Storyboards(和 XIBs)在 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: 中從 Storyboards 例項化的 FlutterViewController 的應用程式,它們有責任適應新的初始化順序。

遷移選項

  • 子類化 FlutterViewController 並將邏輯放在子類的 awakeFromNib 中。
  • Info.plistUIApplicationDelegate 中指定 UISceneDelegate,並將邏輯放在 scene:willConnectToSession:options: 中。有關更多資訊,請參閱 Apple 的文件

示例

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

時間線

#
  • 生效版本:尚未釋出
  • 穩定版本:尚未釋出
  • 未知:Apple 將其警告更改為斷言,並且尚未採用 UISceneDelegate 的 Flutter 應用將在最新 SDK 上啟動時崩潰。

參考資料

#