Skip to main content

採用 UISceneDelegate

了解如何將您的 Flutter iOS 應用程式、加入應用程式(add-to-app)整合或插件 遷移至 Apple 要求的 UIScene 生命週期,使用 FlutterSceneDelegate。

摘要

#

Apple 現在要求 iOS 開發者採用 UIScene 生命週期。 此遷移對應用程式啟動序列應用程式生命週期均有影響。

背景

#

在 WWDC25 期間,Apple 宣布了以下內容:

在 iOS 26 之後的版本中,任何使用最新 SDK 建置的 UIKit 應用程式 都必須使用 UIScene 生命週期,否則將無法啟動。

若要採用 UIScene 生命週期, 請依照符合您使用情境的指南進行操作:

遷移至 UIScene 會改變 AppDelegate 的職責: UISceneDelegate 現在負責處理 UI 生命週期, 而 AppDelegate 仍負責處理程序事件及整體應用程式生命週期。

請將所有與 UI 相關的邏輯從 AppDelegate 移至 對應的 UISceneDelegate 方法中。 遷移至 UIScene 後,UIKit 將不再 呼叫與 UI 狀態相關的 AppDelegate 方法。

遷移 Flutter 應用程式

#

自動遷移

#

自 Flutter 3.41 起,預設支援 UIScene。 如果您的 AppDelegate 尚未經過自訂, Flutter CLI 將自動遷移您的應用程式。

若要觸發遷移,請使用 flutter runflutter build ios 指令建置或執行您的應用程式。 若遷移成功, CLI 將輸出 Finished migration to UIScene lifecycle, 無需進一步操作。 否則,CLI 將發出警告並 提供手動遷移的操作說明。

遷移 AppDelegate

#

以往,Flutter 插件是在 application:didFinishLaunchingWithOptions: 中註冊的。 為了配合新的應用程式啟動序列, 您現在必須在新的 didInitializeImplicitFlutterEngine 回呼(callback)中註冊插件。

  1. 讓您的 AppDelegate 遵循 FlutterImplicitEngineDelegate 協定,並將 GeneratedPluginRegistrant 的註冊移至 didInitializeImplicitFlutterEngine

    my_app/ios/Runner/AppDelegate.swift
    swift
    @objc class AppDelegate: FlutterAppDelegate {
    @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    
      func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
        GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
      }
    }
    
    my_app/ios/Runner/AppDelegate.h
    objc
    @interface AppDelegate : FlutterAppDelegate
    @interface AppDelegate : FlutterAppDelegate <FlutterImplicitEngineDelegate>
    
    my_app/ios/Runner/AppDelegate.m
    objc
    - (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      [GeneratedPluginRegistrant registerWithRegistry:self];
      return [super application:application didFinishLaunchingWithOptions:launchOptions];
    }
    
    - (void)didInitializeImplicitFlutterEngine:(NSObject<FlutterImplicitEngineBridge>*)engineBridge {
      [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry];
    }
    
  2. 如有需要,在 didInitializeImplicitFlutterEngine 中建立方法通道與平台視圖。

    如果您之前在 application:didFinishLaunchingWithOptions: 中 建立了方法通道平台視圖, 請將該邏輯移至 didInitializeImplicitFlutterEngine

    swift
    func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
      // Register plugins with `engineBridge.pluginRegistry`:
      GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
    
      // Create method channels with `engineBridge.applicationRegistrar.messenger()`:
      let batteryChannel = FlutterMethodChannel(
        name: "samples.flutter.dev/battery",
        binaryMessenger: engineBridge.applicationRegistrar.messenger()
      )
    
      // Create platform views with `engineBridge.applicationRegistrar.messenger()`:
      let factory = FLNativeViewFactory(messenger: engineBridge.applicationRegistrar.messenger())
    }
    
    objc
    - (void)didInitializeImplicitFlutterEngine:(NSObject<FlutterImplicitEngineBridge>*)engineBridge {
      // Register plugins with `engineBridge.pluginRegistry`:
      [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry];
    
      // Create method channels with `engineBridge.applicationRegistrar.messenger`:
      FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
          methodChannelWithName:@"samples.flutter.dev/battery"
                binaryMessenger:engineBridge.applicationRegistrar.messenger];
    
      // Create platform views with `engineBridge.applicationRegistrar.messenger`:
      FLNativeViewFactory* factory =
          [[FLNativeViewFactory alloc] initWithMessenger:engineBridge.applicationRegistrar.messenger];
    }
    
  3. 遷移應用程式生命週期事件中的所有自訂邏輯。

    Apple 已棄用與 UI 狀態相關的應用程式生命週期事件。 遷移至 UIScene 生命週期後, UIKit 將不再呼叫這些事件。

    如果您使用了其中某個已棄用的 API, 例如 applicationDidBecomeActive, 您可能需要建立 SceneDelegate 並 遷移至 Scene 生命週期事件。 若要深入了解,請參閱 Apple 關於遷移的官方文件

    如果您實作了自己的 SceneDelegate, 則必須繼承 FlutterSceneDelegate 或 遵循 FlutterSceneLifeCycleProvider 協定。 如需範例,請參閱建立或更新 SceneDelegate

遷移 Info.plist

#

若要完成遷移至 UIScene 生命週期, 請在您的 Info.plist 中加入 Application Scene Manifest 項目。

如 Xcode 編輯器中所示:

Xcode plist editor for UIApplicationSceneManifest

以 XML 格式呈現:

Info.plist
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://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>
</plist>

建立 SceneDelegate(選用)

#

如果您需要存取 SceneDelegate, 可以透過繼承 FlutterSceneDelegate 來建立:

  1. 在 Xcode 中開啟您的應用程式。

  2. Runner 資料夾上按右鍵,然後選擇 New Empty File

    New Empty File option in Xcode

  3. 建立您的 SceneDelegate 類別。

    對於 Swift 專案, 建立一個 SceneDelegate.swift 檔案:

    my_app/ios/Runner/SceneDelegate.swift
    swift
    import Flutter
    import UIKit
    
    class SceneDelegate: FlutterSceneDelegate {}
    

    對於 Objective-C 專案, 建立 SceneDelegate.hSceneDelegate.m 檔案:

    my_app/ios/Runner/SceneDelegate.h
    objc
    #import <Flutter/Flutter.h>
    #import <UIKit/UIKit.h>
    
    @interface SceneDelegate : FlutterSceneDelegate
    
    @end
    
    my_app/ios/Runner/SceneDelegate.m
    objc
    #import "SceneDelegate.h"
    
    @implementation SceneDelegate
    
    @end
    
  4. 在您的 Info.plist 檔案中, 將 Delegate Class NameUISceneDelegateClassName)的值 從 FlutterSceneDelegate 更改為您的新類別名稱。

    對於 Swift 專案,使用 $(PRODUCT_MODULE_NAME).SceneDelegate

    Info.plist
    xml
    <key>UIApplicationSceneManifest</key>
    <dict>
      <!-- ... -->
      <key>UISceneConfigurations</key>
      <dict>
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
          <dict>
            <!-- ... -->
            <key>UISceneDelegateClassName</key>
            <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
            <!-- ... -->
          </dict>
        </array>
      </dict>
    </dict>
    

    對於 Objective-C 專案,使用 SceneDelegate

    Info.plist
    xml
    <key>UIApplicationSceneManifest</key>
    <dict>
      <!-- ... -->
      <key>UISceneConfigurations</key>
      <dict>
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
          <dict>
            <!-- ... -->
            <key>UISceneDelegateClassName</key>
            <string>SceneDelegate</string>
            <!-- ... -->
          </dict>
        </array>
      </dict>
    </dict>
    

遷移加入應用程式(add-to-app)整合

#

FlutterAppDelegate 類似, FlutterSceneDelegate 建議使用但非強制。 FlutterSceneDelegate 會將 Scene 的回呼(callback)轉發, 例如將 openURL 轉發給 local_auth 等插件。

建立或更新 SceneDelegate

#
swift
import UIKit
import Flutter

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
class SceneDelegate: FlutterSceneDelegate {
objc
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@interface SceneDelegate : FlutterSceneDelegate

在 SwiftUI 應用程式中使用 Flutter 時, 您可以選擇性地使用 FlutterAppDelegate 來接收應用程式事件。 若要將其遷移以接收 UIScene 事件, 請進行以下變更:

  1. application:configurationForConnecting:options: 中 將 Scene delegate 設定為 FlutterSceneDelegate

    swift
    @Observable
    class AppDelegate: FlutterAppDelegate {
      ...
      override func application(
        _ application: UIApplication,
        configurationForConnecting connectingSceneSession: UISceneSession,
        options: UIScene.ConnectionOptions
      ) -> UISceneConfiguration {
        let configuration = UISceneConfiguration(
          name: nil,
          sessionRole: connectingSceneSession.role
        )
        configuration.delegateClass = FlutterSceneDelegate.self
        return configuration
      }
    }
    
  2. 如果您的應用程式不支援多個 Scene, 請在目標的 Info 屬性中, 於 Application Scene Manifest 下將 Enable Multiple Scenes 設定為 NO。 SwiftUI 應用程式預設啟用多 Scene 支援。

    Xcode plist editor for UIApplicationSceneManifest

    如果您的應用程式確實支援多個 Scene, 請參閱若您的應用程式支援多個 Scene 以取得進一步說明。

若您無法繼承 FlutterSceneDelegate

#

如果您無法繼承 FlutterSceneDelegate, 請使用 FlutterSceneLifeCycleProvider 協定與 FlutterPluginSceneLifeCycleDelegate 物件, 將 Scene 生命週期事件轉發給 Flutter。

SceneDelegate.swift
swift
import Flutter
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate, FlutterSceneLifeCycleProvider
{
  var sceneLifeCycleDelegate: FlutterPluginSceneLifeCycleDelegate =
    FlutterPluginSceneLifeCycleDelegate()

  var window: UIWindow?

  func scene(
    _ scene: UIScene,
    willConnectTo session: UISceneSession,
    options connectionOptions: UIScene.ConnectionOptions
  ) {
    sceneLifeCycleDelegate.scene(
      scene,
      willConnectTo: session,
      options: connectionOptions
    )
  }

  func sceneDidDisconnect(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneDidDisconnect(scene)
  }

  func sceneWillEnterForeground(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneWillEnterForeground(scene)
  }

  func sceneDidBecomeActive(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneDidBecomeActive(scene)
  }

  func sceneWillResignActive(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneWillResignActive(scene)
  }

  func sceneDidEnterBackground(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneDidEnterBackground(scene)
  }

  func scene(
    _ scene: UIScene,
    openURLContexts URLContexts: Set<UIOpenURLContext>
  ) {
    sceneLifeCycleDelegate.scene(scene, openURLContexts: URLContexts)
  }

  func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    sceneLifeCycleDelegate.scene(scene, continue: userActivity)
  }

  func windowScene(
    _ windowScene: UIWindowScene,
    performActionFor shortcutItem: UIApplicationShortcutItem,
    completionHandler: @escaping (Bool) -> Void
  ) {
    sceneLifeCycleDelegate.windowScene(
      windowScene,
      performActionFor: shortcutItem,
      completionHandler: completionHandler
    )
  }
}
SceneDelegate.h
objc
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate, FlutterSceneLifeCycleProvider>

@property(strong, nonatomic) UIWindow* window;

@property(nonatomic, strong) FlutterPluginSceneLifeCycleDelegate* sceneLifeCycleDelegate;

@end
SceneDelegate.m
objc
@implementation SceneDelegate

- (instancetype)init {
  if (self = [super init]) {
    _sceneLifeCycleDelegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init];
  }
  return self;
}

- (void)scene:(UIScene*)scene
    willConnectToSession:(UISceneSession*)session
                options:(UISceneConnectionOptions*)connectionOptions {
  [self.sceneLifeCycleDelegate scene:scene willConnectToSession:session options:connectionOptions];
}

- (void)sceneDidDisconnect:(UIScene*)scene {
  [self.sceneLifeCycleDelegate sceneDidDisconnect:scene];
}

- (void)sceneDidBecomeActive:(UIScene*)scene {
  [self.sceneLifeCycleDelegate sceneDidBecomeActive:scene];
}

- (void)sceneWillResignActive:(UIScene*)scene {
  [self.sceneLifeCycleDelegate sceneWillResignActive:scene];
}

- (void)sceneWillEnterForeground:(UIScene*)scene {
  [self.sceneLifeCycleDelegate sceneWillEnterForeground:scene];
}

- (void)sceneDidEnterBackground:(UIScene*)scene {
  [self.sceneLifeCycleDelegate sceneDidEnterBackground:scene];
}

- (void)scene:(UIScene*)scene openURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts {
  [self.sceneLifeCycleDelegate scene:scene openURLContexts:URLContexts];
}

- (void)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity {
  [self.sceneLifeCycleDelegate scene:scene continueUserActivity:userActivity];
}

- (void)windowScene:(UIWindowScene*)windowScene
    performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
               completionHandler:(void (^)(BOOL))completionHandler {
  [self.sceneLifeCycleDelegate windowScene:windowScene
            performActionForShortcutItem:shortcutItem completionHandler:completionHandler];
}

若您的應用程式支援多個 Scene

#

當啟用多個 Scene(UIApplicationSupportsMultipleScenes)時, Flutter 無法在初始 Scene 連線階段自動將 FlutterEngine 連結到對應的 UIScene

為確保 Flutter 插件能接收初始 Scene 設定選項 (例如透過 UIScene.ConnectionOptions 酬載(payload)傳遞的深層連結 URL 或捷徑項目), 您必須在 scene:willConnectToSession:options: 方法中, 手動將 FlutterEngine 向您的 FlutterSceneDelegateFlutterPluginSceneLifeCycleDelegate 注冊。

如果您未執行此手動注冊, FlutterEngine 仍會在 FlutterViewController 建立的視圖 加入到活躍視圖層級時自動進行注冊。 但在那個時間點,引擎及其插件已錯過了 在 willConnectToSession: 期間傳遞的任何啟動連線事件。

SceneDelegate.swift
swift
import Flutter
import FlutterPluginRegistrant
import UIKit

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)

    // If using FlutterSceneDelegate:
    self.registerSceneLifeCycle(with: flutterEngine)

    // If using FlutterSceneLifeCycleProvider:
    // sceneLifeCycleDelegate.registerSceneLifeCycle(with: flutterEngine)

    let viewController = ViewController(engine: flutterEngine)
    window?.rootViewController = viewController
    window?.makeKeyAndVisible()
    super.scene(scene, willConnectTo: session, options: connectionOptions)
  }
}
SceneDelegate.h
objc
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>

@interface SceneDelegate : FlutterSceneDelegate
@property(nonatomic, strong) FlutterEngine *flutterEngine;
@end
SceneDelegate.m
objc
#import "SceneDelegate.h"
#import "ViewController.h"

@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];

  // If using FlutterSceneDelegate:
  [self registerSceneLifeCycleWithFlutterEngine:self.flutterEngine];

  // If using FlutterSceneLifeCycleProvider:
  // [self.sceneLifeCycleDelegate 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

如果您手動將 FlutterEngine 向 Scene 注冊, 當 FlutterEngine 建立的視圖切換 Scene 時, 您也必須取消注冊。

swift
// If using FlutterSceneDelegate:
self.unregisterSceneLifeCycle(with: flutterEngine)

// If using FlutterSceneLifeCycleProvider:
sceneLifeCycleDelegate.unregisterSceneLifeCycle(with: flutterEngine)
objc
// If using FlutterSceneDelegate:
[self unregisterSceneLifeCycleWithFlutterEngine:self.flutterEngine];

// If using FlutterSceneLifeCycleProvider:
[self.sceneLifeCycleDelegate unregisterSceneLifeCycleWithFlutterEngine:self.flutterEngine];

遷移 Flutter 插件

#

並非所有插件都使用生命週期事件。 但如果您的插件有使用, 請依照以下步驟將其遷移至 UIKit 基於 Scene 的生命週期:

  1. 在您的 pubspec.yaml 中更新 Dart 與 Flutter SDK 版本。

    此遷移所需的 API 自 Flutter 3.38 起提供:

    pubspec.yaml
    yaml
    environment:
      sdk: ^3.10.0
      flutter: ">=3.38.0"
    
  2. 採用 FlutterSceneLifeCycleDelegate 協定。

    swift
    public final class MyPlugin: NSObject, FlutterPlugin {
    public final class MyPlugin: NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate {
    
    objc
    @interface MyPlugin : NSObject<FlutterPlugin>
    @interface MyPlugin : NSObject<FlutterPlugin, FlutterSceneLifeCycleDelegate>
    
  3. 將插件注冊為 UISceneDelegate 呼叫的接收者。

    為了繼續支援尚未遷移至 UIScene 生命週期的應用程式, 建議保留對應用程式 delegate 的注冊, 並同時保留 AppDelegate 事件。

    swift
    public static func register(with registrar: FlutterPluginRegistrar) {
      ...
      registrar.addApplicationDelegate(instance)
      registrar.addSceneDelegate(instance)
    }
    
    objc
    + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
      ...
      [registrar addApplicationDelegate:instance];
      [registrar addSceneDelegate:instance];
    }
    
  4. 加入您的插件所需的 Scene 事件。

    大多數 AppDelegate UI 事件都有一對一的替代方案, 如下表所示。 如需每個事件的詳細資訊,請參閱 Apple 關於 UISceneDelegateUIWindowSceneDelegate 的文件。

    一旦確認了替代應用程式事件的 Scene 事件, 請實作對應的 FlutterSceneLifeCycleDelegate 方法。 以下程式碼片段展示了 FlutterSceneLifeCycleDelegate 支援的每個 Scene 事件的簽名; 只需實作您的插件所需的部分。

    swift
    public func scene(
      _ scene: UIScene,
      willConnectTo session: UISceneSession,
      options connectionOptions: UIScene.ConnectionOptions?
    ) -> Bool { }
    
    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 { }
    
    public func scene(
      _ scene: UIScene,
      continue userActivity: NSUserActivity
    ) -> Bool { }
    
    public func windowScene(
      _ windowScene: UIWindowScene,
      performActionFor shortcutItem: UIApplicationShortcutItem,
      completionHandler: @escaping (Bool) -> Void
    ) -> Bool { }
    
    objc
    - (BOOL)scene:(UIScene*)scene
        willConnectToSession:(UISceneSession*)session
                     options:(nullable UISceneConnectionOptions*)connectionOptions { }
    
    - (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 { }
    
    - (BOOL)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity { }
    
    - (BOOL)windowScene:(UIWindowScene*)windowScene
        performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
                   completionHandler:(void (^)(BOOL succeeded))completionHandler { }
    
  5. 將啟動邏輯從 application:willFinishLaunchingWithOptions:application:didFinishLaunchingWithOptions: 移至 scene:willConnectToSession:options:

    儘管 application:willFinishLaunchingWithOptions:application:didFinishLaunchingWithOptions: 尚未被棄用, 但在遷移至 UIScene 生命週期後,其啟動選項將為 nil。 請將依賴啟動選項的任何邏輯移至 scene:willConnectToSession:options: 事件中。

  6. 選用:遷移其他已棄用的 API,以便未來支援多個 Scene。

    請改為透過 viewController 存取 windowScene, 如以下範例所示。

    swift
    public class MyPlugin: NSObject, FlutterPlugin {
      var registrar: FlutterPluginRegistrar
    
      init(registrar: FlutterPluginRegistrar) {
        self.registrar = registrar
      }
    
      public static func register(with registrar: FlutterPluginRegistrar) {
        let instance = MyPlugin()
        let instance = MyPlugin(registrar: registrar)
      }
    
      func someMethod() {
        let screen = UIScreen.main
        let screen = self.registrar.viewController?.view.window?.windowScene?.screen
    
        let window = UIApplication.shared.delegate?.window
        let window = self.registrar.viewController?.view.window
    
        let keyWindow = UIApplication.shared.keyWindow
        if #available(iOS 15.0, *) {
          let keyWindow = self.registrar.viewController?.view.window?.windowScene?.keyWindow
        } else {
          let keyWindow = self.registrar.viewController?.view.window?.windowScene?.windows
            .filter({ $0.isKeyWindow }).first
        }
    
        let windows = UIApplication.shared.windows
        let windows = self.registrar.viewController?.view.window?.windowScene?.windows
      }
    }
    
    objc
    @interface MyPlugin ()
    @property(nonatomic, weak) NSObject<FlutterPluginRegistrar> *registrar;
    - (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar;
    @end
    
    @implementation MyPlugin
    
    - (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
      self = [super init];
      if (self) {
        _registrar = registrar;
      }
      return self;
    }
    
    + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
      MyPlugin *instance = [[MyPlugin alloc] init];
      MyPlugin *instance = [[MyPlugin alloc] initWithRegistrar:registrar];
    }
    
    - (void)someMethod {
      UIScreen *screen = [UIScreen mainScreen];
      UIScreen *screen = self.registrar.viewController.view.window.windowScene.screen;
    
      UIWindow *window = [UIApplication sharedApplication].delegate.window;
      UIWindow *window = self.registrar.viewController.view.window;
    
      UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
      if (@available(iOS 15.0, *)) {
        UIWindow *keyWindow = self.registrar.viewController.view.window.windowScene.keyWindow;
      } else {
        for (UIWindow *window in self.registrar.viewController.view.window.windowScene.windows) {
          if (window.isKeyWindow) {
            UIWindow *keyWindow = window;
          }
        }
      }
    
      NSArray<UIWindow *> *windows = [UIApplication sharedApplication].windows;
      NSArray<UIWindow *> *windows = self.registrar.viewController.view.window.windowScene.windows;
    }
    

自訂 FlutterViewController 的使用方式

#

如果您的應用程式因建立平台通道以外的原因, 在 application:didFinishLaunchingWithOptions: 中 從 Storyboard 實例化了 FlutterViewController, 您必須配合新的初始化順序進行調整。 請使用以下其中一種遷移方式:

  • 繼承 FlutterViewController 並將邏輯放入 子類別的 awakeFromNib 方法中。
  • Info.plistUIApplicationDelegate 中指定 UISceneDelegate, 並將邏輯放入 scene:willConnectToSession:options: 中。 若要深入了解,請參閱 Apple 的官方文件

範例

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

隱藏遷移警告

#

若要隱藏 Flutter CLI 關於遷移至 UIScene 的警告, 請在您的 pubspec.yaml 中加入以下內容:

pubspec.yaml
yaml
flutter:
  config:
    enable-uiscene-migration: false

暫時停用 UIScene

#

若要_暫時_停用 UIScene 支援,請在您的 Info.plist 中 在 Application Scene Manifest 前加上底線(_):

Xcode plist editor with an underscore in front of Application Scene Manifest

當您準備好重新啟用 UIScene 支援時,移除底線即可。

時間軸

#

已落地版本:3.38.0-0.1.pre
穩定版本:3.38

Apple 尚未宣布何時強制執行 UIScene 要求。 一旦 Apple 將其警告改為斷言, 尚未採用 UIScene 生命週期的 Flutter 應用程式 在使用最新 SDK 建置時將在啟動時崩潰。

參考資料

#