UISceneDelegate adoption
Summary
#Apple now requires iOS developers to adopt the UIScene life cycle. This migration has implications on the app launch sequence and app life cycle.
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 use the UIScene lifecycle with Flutter, migrate the following support:
- All Flutter apps that support iOS - See the migration guide for Flutter apps
 - Flutter embedded in iOS native apps - See the migration guide for adding Flutter to an existing app
 - Flutter plugins that use iOS application lifecycle events - See the migration guide for plugins
 
Migrating to UIScene shifts the AppDelegate's role—the UI lifecycle is now handled by the UISceneDelegate. The AppDelegate remains responsible for process events and the overall application lifecycle. All UI-related logic should be moved from the AppDelegate to the corresponding UISceneDelegate methods. After migrating to UIScene, UIKit won't call AppDelegate methods related to UI state.
Migration guide for Flutter apps
#Auto-Migrate (Experimental)
#The Flutter CLI can automatically migrate your app if your AppDelegate has not been customized.
- Enable UIScene Migration Feature
 
flutter config --enable-uiscene-migration
                    
                    
                    
                  - Build or run your app
 
flutter run
or
flutter build ios
                    
                    
                    
                  If the migration succeeds, you will see a log that says "Finished migration to UIScene lifecycle". Otherwise, it warns you to migrate manually using the included instructions. If the migration succeeds, no further action is required!
Migrate AppDelegate
#
                  Previously, Flutter plugins were registered in
                  application:didFinishLaunchingWithOptions:. To accomodate the new app launch
                  sequence, plugin registration must now be handled in a new callback called
                  didInitializeImplicitFlutterEngine.
                
- Add 
FlutterImplicitEngineDelegateand moveGeneratedPluginRegistrant. 
@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)
}
                    
                    
                    
                  @interface AppDelegate : FlutterAppDelegate
@interface AppDelegate : FlutterAppDelegate <FlutterImplicitEngineDelegate>
                    
                    
                    
                  - (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];
}
                    
                    
                    
                  - 
                    Create method channels and platform views in
                    
didInitializeImplicitFlutterEngine, if applicable. 
                  If you previously created method channels or
                  platform views in
                  application:didFinishLaunchingWithOptions:,
                  move that logic to didInitializeImplicitFlutterEngine.
                
  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())
  }
                    
                    
                    
                    func 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];
  }
                    
                    
                    
                  - Migrate any custom logic within application life cycle events.
 
Apple has deprecated application life cycle events related to UI state. After migrating to UIScene lifecycle, UIKit will no longer call these events.
                  If you were using one of these depreacted APIs, such as
                  applicationDidBecomeActive,
                  you will likely need to create a SceneDelegate and migrate to scene life cycle
                  events. See Apple's
                    documenation
                  on migrating.
                
                  If you implement your own SceneDelegate, you must subclass it with
                  FlutterSceneDelegate or conform to the FlutterSceneLifeCycleProvider
                  
                  protocol. See the following
                    examples.
                
Migrate Info.plist
#
                  To complete the migration to the UIScene lifecycle, add an Application Scene Manifest to your Info.plist.
                
As seen in Xcode's editor:
                  
                
As 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>
                    
                    
                    
                  Create a SceneDelegate (Optional)
#
                  If you need access to the SceneDelegate, you can create one by
                  subclassing FlutterSceneDelegate.
                
- Open your app in Xcode
 - Right click the Runner folder and select New Empty File
 
                  
                
For Swift projects, create a SceneDelegate.swift:
import Flutter
import UIKit
class SceneDelegate: FlutterSceneDelegate {
}
                    
                    
                    
                  For Objective-C projects, create a SceneDelegate.h and SceneDelegate.m:
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface SceneDelegate : FlutterSceneDelegate
@end
                    
                    
                    
                  #import "SceneDelegate.h"
@implementation SceneDelegate
@end
                    
                    
                    
                  - 
                    Change the "Delegate Class Name" (
UISceneDelegateClassName) in the Info.plist fromFlutterSceneDelegateto$(PRODUCT_MODULE_NAME).SceneDelegate. 
Migration guide for adding Flutter to existing app (Add to App)
#
                  Similar to the FlutterAppDelegate, the FlutterSceneDelgate is recommended
                  but not required. The FlutterSceneDelgate forwards scene callbacks, such as
                  openURL
                   to plugins such as local_auth.
                
Create/Update a SceneDelegate (UIKit)
#import UIKit
import Flutter
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
class SceneDelegate: FlutterSceneDelegate {
                    
                    
                    
                  @interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@interface SceneDelegate : FlutterSceneDelegate
                    
                    
                    
                  Create/Update a SceneDelegate (SwiftUI)
#When using Flutter in a SwifUI app, you can optionally use a FlutterAppDelegate to receive application events. To migrate that to use UIScene events, you can make the following changes:
- 
                    Set the Scene Delegate to 
FlutterSceneDelegateinapplication:configurationForConnecting:options:. 
@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
  }
}
                    
                    
                    
                  - 
                    If your app does not support multiple scenes, set 
Enable Multiple ScenestoNOunderApplication Scene Manifestin your target's Info properties. This is enabled by default for SwiftUI apps. 
                  
                
Otherwise, see If your app supports multiple scenes for further instructions.
If you can't directly make FlutterSceneDelegate a subclass
#
                  If you can't directly make FlutterSceneDelegate a subclass, you can use the
                  FlutterSceneLifeCycleProvider protocol and
                  FlutterPluginSceneLifeCycleDelegate object to forward scene life cycle events
                  to Flutter.
                
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
    )
  }
}
                    
                    
                    
                  @interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate, FlutterSceneLifeCycleProvider>
@property(strong, nonatomic) UIWindow* window;
@property (nonatomic,strong) FlutterPluginSceneLifeCycleDelegate *sceneLifeCycleDelegate;
@end
                    
                    
                    
                  @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 is enabled (UIApplicationSupportsMultipleScenes), Flutter cannot automatically associate a
                  FlutterEngine with a scene during the scene connection phase. In order for
                  plugins to receive launch connection information, the FlutterEngine must be
                  manually registered with either the FlutterSceneDelegate or
                  FlutterPluginSceneLifeCycleDelegate during
                  scene:willConnectToSession:options:. Otherwise, once the view, created by the
                  FlutterViewController and FlutterEngine, is added to the view heirarchy,
                  the FlutterEngine will automatically register for scene events.
                
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)
  }
}
                    
                    
                    
                  #import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
@interface SceneDelegate : FlutterSceneDelegate
@property (nonatomic, strong) FlutterEngine *flutterEngine;
@end
                    
                    
                    
                  #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.
                
// If using FlutterSceneDelegate:
self.unregisterSceneLifeCycle(with: flutterEngine)
// If using FlutterSceneLifeCycleProvider:
sceneLifeCycleDelegate.unregisterSceneLifeCycle(with: flutterEngine)
                    
                    
                    
                  // If using FlutterSceneDelegate:
[self unregisterSceneLifeCycleWithFlutterEngine:self.flutterEngine];
// If using FlutterSceneLifeCycleProvider:
[self.sceneLifeCycleDelegate unregisterSceneLifeCycleWithFlutterEngine:self.flutterEngine];
                    
                    
                    
                  Migration guide for Flutter plugins
#Not all plugins use lifecycle events. If your plugin does, though, you will need to migrate to UIKit's scene-based lifecycle.
- Update the Dart and Flutter SDK versions in your pubspec.yaml
 
environment:
  sdk: ^3.10.0-290.1.beta
  flutter: ">=3.38.0-0.1.pre"
                    
                    
                    
                  - Adopt the 
FlutterSceneLifeCycleDelegateprotocol 
public final class MyPlugin: NSObject, FlutterPlugin {
public final class MyPlugin: NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate {
                    
                    
                    
                  @interface MyPlugin : NSObject<FlutterPlugin>
@interface MyPlugin : NSObject<FlutterPlugin, FlutterSceneLifeCycleDelegate>
                    
                    
                    
                  - Registers the plugin as a receiver of 
UISceneDelegatecalls. 
To continue supporting apps that have not migrated to the UIScene lifecycle yet, you might consider remaining registered to the App Delegate and keeping the App Delegate events as well.
public static func register(with registrar: FlutterPluginRegistrar) {
  ...
  registrar.addApplicationDelegate(instance)
  registrar.addSceneDelegate(instance)
}
                    
                    
                    
                  + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
  ...
  [registrar addApplicationDelegate:instance];
  [registrar addSceneDelegate:instance];
}
                    
                    
                    
                  - Add one or more of the following scene events that are needed for your plugin.
 
                  Most App Delegate UI events have a 1-to-1 replacement. To see details for each
                  event, visit Apple's documentation on
                  UISceneDelegate
                   and UIWindowSceneDelegate.
                
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 { }
                    
                    
                    
                  - (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 { }
                    
                    
                    
                  - 
                    Move launch logic from 
application:willFinishLaunchingWithOptions:andapplication:didFinishLaunchingWithOptions:toscene:willConnectToSession:options:. 
                  Despite application:willFinishLaunchingWithOptions: and
                  application:didFinishLaunchingWithOptions: not being deprecated, after
                  migrating to the UIScene lifecycle, the launch options will be nil. Any logic
                  performed here related to the launch options should be moved to the
                  scene:willConnectToSession:options: event.
                
Bespoke FlutterViewController usage
#
                  For apps that use a FlutterViewController instantiated from Storyboards in
                  application:didFinishLaunchingWithOptions: for reasons other than
                  creating platform channels, it is their responsibility to
                  accommodate the new initialization order.
                
Migration options:
- 
                    Subclass 
FlutterViewControllerand put the logic in the subclasses'awakeFromNib. - 
                    Specify a 
UISceneDelegatein theInfo.plistor in theUIApplicationDelegateand put the logic inscene:willConnectToSession:options:. For more information, check out Apple's documentation. 
Example
#@objc class MyViewController: FlutterViewController {
  override func awakeFromNib() {
    self.awakeFromNib()
    doSomethingWithFlutterViewController(self)
  }
}
                    
                    
                    
                  Hide Migration Warning
#To hide the Flutter CLI warning about migrating to UIScene, add the following to your pubspec.yaml:
flutter:
  config:
    enable-uiscene-migration: false
                    
                    
                    
                  Timeline
#- Landed in main: TBD
 - Landed in stable: TBD
 - 
                    Unknown: Apple changes their warning to an assert and Flutter apps that
                    haven't adopted 
UISceneDelegatewill start crashing on startup with the latest SDK. 
References
#- Issue 167267 - The initial reported issue.
 
Unless stated otherwise, the documentation on this site reflects Flutter 3.35.5. Page last updated on 2025-10-28. View source or report an issue.