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:
- For all Flutter apps that support iOS, follow the migration guide for Flutter apps.
- For Flutter apps embedded in existing native iOS apps, follow the migration guide for add-to-app.
- For Flutter plugins that use iOS application lifecycle events, follow the migration guide for Flutter plugins.
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.
-
Conform your
AppDelegateto theFlutterImplicitEngineDelegateprotocol and move theGeneratedPluginRegistrantregistration todidInitializeImplicitFlutterEngine.my_app/ios/Runner/AppDelegate.swiftswift@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.hobjc@interface AppDelegate : FlutterAppDelegate @interface AppDelegate : FlutterAppDelegate <FlutterImplicitEngineDelegate>my_app/ios/Runner/AppDelegate.mobjc- (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]; } -
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 todidInitializeImplicitFlutterEngine.swiftfunc 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]; } -
Migrate any custom logic within application lifecycle events.
Apple has deprecated application lifecycle events related to UI state. After you migrate to the
UIScenelifecycle, UIKit no longer calls these events.If you used one of these deprecated APIs, such as
applicationDidBecomeActive, you likely need to create aSceneDelegateand migrate to scene lifecycle events. To learn more, see Apple's documentation on migrating.If you implement your own
SceneDelegate, you must subclassFlutterSceneDelegateor conform to theFlutterSceneLifeCycleProviderprotocol. 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:
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>
</plist>
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, then select New Empty File.

-
Create your
SceneDelegateclass.For Swift projects, create a
SceneDelegate.swiftfile:my_app/ios/Runner/SceneDelegate.swiftswiftimport Flutter import UIKit class SceneDelegate: FlutterSceneDelegate {}For Objective-C projects, create a
SceneDelegate.hand aSceneDelegate.mfile:my_app/ios/Runner/SceneDelegate.hobjc#import <Flutter/Flutter.h> #import <UIKit/UIKit.h> @interface SceneDelegate : FlutterSceneDelegate @endmy_app/ios/Runner/SceneDelegate.mobjc#import "SceneDelegate.h" @implementation SceneDelegate @end -
In your
Info.plistfile, change the Delegate Class Name (UISceneDelegateClassName) value fromFlutterSceneDelegateto your new class.For Swift projects, use
$(PRODUCT_MODULE_NAME).SceneDelegate:Info.plistxml<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.plistxml<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
#import UIKit
import Flutter
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
class SceneDelegate: FlutterSceneDelegate {
@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:
-
Set the scene delegate to
FlutterSceneDelegateinapplication: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 } } -
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.

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.
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 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:.
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];
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:
-
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.yamlyamlenvironment: sdk: ^3.10.0 flutter: ">=3.38.0" -
Adopt the
FlutterSceneLifeCycleDelegateprotocol.swiftpublic final class MyPlugin: NSObject, FlutterPlugin { public final class MyPlugin: NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate {objc@interface MyPlugin : NSObject<FlutterPlugin> @interface MyPlugin : NSObject<FlutterPlugin, FlutterSceneLifeCycleDelegate> -
Register the plugin as a receiver of
UISceneDelegatecalls.To continue supporting apps that haven't yet migrated to the
UIScenelifecycle, consider remaining registered to the application delegate and keeping theAppDelegateevents as well.swiftpublic static func register(with registrar: FlutterPluginRegistrar) { ... registrar.addApplicationDelegate(instance) registrar.addSceneDelegate(instance) }objc+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { ... [registrar addApplicationDelegate:instance]; [registrar addSceneDelegate:instance]; } -
Add the scene events that your plugin needs.
Most
AppDelegateUI events have a one-to-one replacement, as shown in the following table. For details about each event, visit Apple's documentation forUISceneDelegateandUIWindowSceneDelegate.Once you identify the scene events that replace the application events your plugin relied on, implement the corresponding
FlutterSceneLifeCycleDelegatemethods. The following snippets show the signature of each scene event thatFlutterSceneLifeCycleDelegatesupports; implement only the ones your plugin needs.swiftpublic 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 { } -
Move launch logic from
application:willFinishLaunchingWithOptions:andapplication:didFinishLaunchingWithOptions:toscene:willConnectToSession:options:.Although
application:willFinishLaunchingWithOptions:andapplication:didFinishLaunchingWithOptions:aren't deprecated, their launch options arenilafter you migrate to theUIScenelifecycle. Move any logic that relies on the launch options to thescene:willConnectToSession:options:event. -
Optional: Migrate other deprecated APIs to support multiple scenes in the future.
Instead of accessing these APIs, access the
windowScenethrough theviewController, as shown in the following examples.swiftpublic 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
FlutterViewControllerand put the logic in the subclass'sawakeFromNibmethod. -
Specify a
UISceneDelegatein theInfo.plistor in theUIApplicationDelegate, and put the logic inscene:willConnectToSession:options:. To learn more, check out Apple's documentation.
Example
#@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:
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:
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
#- Issue 167267: The initial reported issue.
Unless stated otherwise, the documentation on this site reflects Flutter 3.44.0. Page last updated on 2026-06-10. View source or report an issue.