Skip to main content

UISceneDelegate adoption

Learn how to migrate your Flutter iOS app, add-to-app integration, or plugin to Apple's required UIScene lifecycle using FlutterSceneDelegate.

Summary

#

Apple now requires iOS developers to adopt the UIScene lifecycle. This migration has implications for the app launch sequence and app lifecycle.

Background

#

During WWDC25, Apple announced the following:

In the release following iOS 26, any UIKit app built with the latest SDK will be required to use the UIScene life cycle, otherwise it will not launch.

To adopt the UIScene lifecycle, follow the guide that corresponds to your use case:

Migrating to UIScene shifts the role of the AppDelegate: the UISceneDelegate now handles the UI lifecycle, while the AppDelegate remains responsible for process events and the overall application lifecycle.

Move all UI-related logic from the AppDelegate to the corresponding UISceneDelegate methods. After you migrate to UIScene, UIKit no longer calls AppDelegate methods related to UI state.

Migrate a Flutter app

#

Auto-migrate

#

As of Flutter 3.41, UIScene is supported by default. If your AppDelegate hasn't been customized, the Flutter CLI automatically migrates your app.

To trigger the migration, build or run your app with the flutter run or flutter build ios commands. If the migration succeeds, the CLI outputs Finished migration to UIScene lifecycle and no further action is required. Otherwise, the CLI warns you and provides instructions to migrate manually.

Migrate AppDelegate

#

Previously, Flutter plugins were registered in application:didFinishLaunchingWithOptions:. To accommodate the new app launch sequence, you must now register plugins in a new didInitializeImplicitFlutterEngine callback.

  1. Conform your AppDelegate to the FlutterImplicitEngineDelegate protocol and move the GeneratedPluginRegistrant registration to 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. If applicable, create method channels and platform views in didInitializeImplicitFlutterEngine.

    If you previously created method channels or platform views in application:didFinishLaunchingWithOptions:, move that logic to 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. Migrate any custom logic within application lifecycle events.

    Apple has deprecated application lifecycle events related to UI state. After you migrate to the UIScene lifecycle, UIKit no longer calls these events.

    If you used one of these deprecated APIs, such as applicationDidBecomeActive, you likely need to create a SceneDelegate and migrate to scene lifecycle events. To learn more, see Apple's documentation on migrating.

    If you implement your own SceneDelegate, you must subclass FlutterSceneDelegate or conform to the FlutterSceneLifeCycleProvider protocol. For examples, see Create or update a SceneDelegate.

Migrate Info.plist

#

To complete the migration to the UIScene lifecycle, add an Application Scene Manifest entry to your Info.plist.

As shown in Xcode's editor:

Xcode plist editor for UIApplicationSceneManifest

As 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>

Create a SceneDelegate (optional)

#

If you need access to the SceneDelegate, you can create one by subclassing FlutterSceneDelegate:

  1. Open your app in Xcode.

  2. Right-click the Runner folder, then select New Empty File.

    New Empty File option in Xcode

  3. Create your SceneDelegate class.

    For Swift projects, create a SceneDelegate.swift file:

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

    For Objective-C projects, create a SceneDelegate.h and a SceneDelegate.m file:

    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. In your Info.plist file, change the Delegate Class Name (UISceneDelegateClassName) value from FlutterSceneDelegate to your new class.

    For Swift projects, use $(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>
    

    For Objective-C projects, use 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>
    

Migrate an add-to-app integration

#

Similar to the FlutterAppDelegate, the FlutterSceneDelegate is recommended but not required. The FlutterSceneDelegate forwards scene callbacks, such as openURL, to plugins such as local_auth.

Create or update a SceneDelegate

#
swift
import UIKit
import Flutter

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

When using Flutter in a SwiftUI app, you can optionally use a FlutterAppDelegate to receive application events. To migrate it to receive UIScene events, make the following changes:

  1. Set the scene delegate to FlutterSceneDelegate in application:configurationForConnecting:options::

    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. If your app doesn't support multiple scenes, set Enable Multiple Scenes to NO under Application Scene Manifest in your target's Info properties. Multiple-scene support is enabled by default for SwiftUI apps.

    Xcode plist editor for UIApplicationSceneManifest

    If your app does support multiple scenes, see If your app supports multiple scenes for further instructions.

If you can't subclass FlutterSceneDelegate

#

If you can't subclass FlutterSceneDelegate, use the FlutterSceneLifeCycleProvider protocol and a FlutterPluginSceneLifeCycleDelegate object to forward scene lifecycle events to 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];
}

If your app supports multiple scenes

#

When multiple scenes are enabled (UIApplicationSupportsMultipleScenes), Flutter can't automatically connect a FlutterEngine to its corresponding UIScene during the initial scene connection phase.

To ensure that Flutter plugins can receive the initial scene setup options (such as deep link URLs or shortcut items passed inside the UIScene.ConnectionOptions payload), you must manually register the FlutterEngine with either your FlutterSceneDelegate or your FlutterPluginSceneLifeCycleDelegate inside the scene:willConnectToSession:options: method.

If you don't perform this manual registration, the FlutterEngine still registers itself automatically once the view created by the FlutterViewController is added to the active view hierarchy. However, by that point, the engine and its plugins have already missed any launch connection events passed during 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

If you manually register a FlutterEngine with a scene, you must also unregister it if the view created by the FlutterEngine changes scenes.

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

Migrate a Flutter plugin

#

Not all plugins use lifecycle events. However, if your plugin does, migrate it to UIKit's scene-based lifecycle as follows:

  1. Update the Dart and Flutter SDK versions in your pubspec.yaml.

    The APIs required for this migration are available starting in Flutter 3.38:

    pubspec.yaml
    yaml
    environment:
      sdk: ^3.10.0
      flutter: ">=3.38.0"
    
  2. Adopt the FlutterSceneLifeCycleDelegate protocol.

    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. Register the plugin as a receiver of UISceneDelegate calls.

    To continue supporting apps that haven't yet migrated to the UIScene lifecycle, consider remaining registered to the application delegate and keeping the AppDelegate events as well.

    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. Add the scene events that your plugin needs.

    Most AppDelegate UI events have a one-to-one replacement, as shown in the following table. For details about each event, visit Apple's documentation for UISceneDelegate and UIWindowSceneDelegate.

    Once you identify the scene events that replace the application events your plugin relied on, implement the corresponding FlutterSceneLifeCycleDelegate methods. The following snippets show the signature of each scene event that FlutterSceneLifeCycleDelegate supports; implement only the ones your plugin needs.

    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. Move launch logic from application:willFinishLaunchingWithOptions: and application:didFinishLaunchingWithOptions: to scene:willConnectToSession:options:.

    Although application:willFinishLaunchingWithOptions: and application:didFinishLaunchingWithOptions: aren't deprecated, their launch options are nil after you migrate to the UIScene lifecycle. Move any logic that relies on the launch options to the scene:willConnectToSession:options: event.

  6. Optional: Migrate other deprecated APIs to support multiple scenes in the future.

    Instead of accessing these APIs, access the windowScene through the viewController, as shown in the following examples.

    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;
    }
    

Bespoke FlutterViewController usage

#

If your app instantiates a FlutterViewController from storyboards in application:didFinishLaunchingWithOptions: for reasons other than creating platform channels, you must accommodate the new initialization order. To do so, use one of the following migration options:

  • Subclass FlutterViewController and put the logic in the subclass's awakeFromNib method.
  • Specify a UISceneDelegate in the Info.plist or in the UIApplicationDelegate, and put the logic in scene:willConnectToSession:options:. To learn more, check out Apple's documentation.

Example

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

Hide migration warning

#

To hide the Flutter CLI warning about migrating to UIScene, add the following to your pubspec.yaml:

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

Temporarily disable UIScene

#

To temporarily disable UIScene support, add an underscore (_) in front of Application Scene Manifest in your Info.plist:

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

When you're ready to re-enable UIScene support, remove the underscore.

Timeline

#

Landed in version: 3.38.0-0.1.pre
In stable release: 3.38

Apple hasn't yet announced when it will enforce the UIScene requirement. Once Apple changes its warning to an assertion, Flutter apps that haven't adopted the UIScene lifecycle will crash on startup when built with the latest SDK.

References

#