UIScene adoption
Learn how to migrate your Flutter iOS app, add-to-app integration, or plugin to Apple's required UIScene lifecycle.
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
#Flutter doesn't fully support multiple scenes yet, but support is in progress.
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. -
If your plugin uses an API that must be called before
application:didFinishLaunchingWithOptions:completes, you should expose a public method that app developers can call directly in their AppDelegate.The following Apple APIs must be configured before app launch finishes:
BGTaskScheduler.registerForTaskWithIdentifier:usingQueue:launchHandler:UNUserNotificationCenterDelegateHKHealthStore.enableBackgroundDeliveryForType:frequency:withCompletion:
For example, to support
BGTaskScheduler:swift// Plugin public class BGTaskPlugin: NSObject, FlutterPlugin { public static let shared = BGTaskPlugin() public static func register(with registrar: FlutterPluginRegistrar) { let channel = ... registrar.addMethodCallDelegate(shared, channel: channel) registrar.addApplicationDelegate(shared) } // A new API for apps to call in their `AppDelegate`'s `application:didFinishLaunchingWithOptions:` public func registerBackgroundHandler(identifier: String) { BGTaskScheduler.register(forTaskWithIdentifier: identifier, using: .main) { task in // Handle background task execution } } public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable : Any] = [:]) -> Bool { BGTaskScheduler.register(forTaskWithIdentifier: identifier, using: .main) { task in // Handle background task execution } return true } } // App class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { BGTaskPlugin.shared.registerBackgroundHandler(identifier: "com.example.task") return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }objc// Plugin @interface BGTaskPlugin : NSObject <FlutterPlugin> + (instancetype)sharedInstance; - (void)registerBackgroundHandlerWithIdentifier:(NSString *)identifier; @end @implementation BGTaskPlugin + (instancetype)sharedInstance { static BGTaskPlugin *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[BGTaskPlugin alloc] init]; }); return sharedInstance; } + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { FlutterMethodChannel *channel = ... [registrar addMethodCallDelegate:[BGTaskPlugin sharedInstance] channel:channel]; [registrar addApplicationDelegate:[BGTaskPlugin sharedInstance]]; } // A new API for apps to call in their `AppDelegate`'s `application:didFinishLaunchingWithOptions:` - (void)registerBackgroundHandlerWithIdentifier:(NSString *)identifier { [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:identifier usingQueue:dispatch_get_main_queue() launchHandler:^(BGTask *task) { // Handle background task execution }]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:identifier usingQueue:dispatch_get_main_queue() launchHandler:^(BGTask *task) { // Handle background task execution }]; return YES; } @end // App @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[BGTaskPlugin sharedInstance] registerBackgroundHandlerWithIdentifier:@"com.example.task"]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @endThis change is required due to UIScene changing the app launch sequence. For apps that adopt
UIScene, Flutter callsapplication:willFinishLaunchingWithOptions:andapplication:didFinishLaunchingWithOptions:during thescene:willConnectToSession:options:callback. -
Migrate other deprecated APIs to properly access the
viewController,screen, orwindow.Because
UIApplication.shared.delegate.windowisnilafter adoptingUIScene, you should access theviewControllervia the plugin registrar'sviewControllerproperty and retrieve the window orwindowScenefrom there, 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-07-20. View source or report an issue.