Saltar al contenido principal

Adopción de UISceneDelegate

Aprende cómo migrar tu app iOS de Flutter, integración add-to-app, o plugin al ciclo de vida UIScene requerido por Apple usando FlutterSceneDelegate.

Resumen

#

Apple ahora requiere que los desarrolladores de iOS adopten el ciclo de vida UIScene. Esta migración tiene implicaciones para la secuencia de inicio de la aplicación y el ciclo de vida de la aplicación.

Contexto

#

Durante la WWDC25, Apple anunció lo siguiente:

En la versión posterior a iOS 26, cualquier aplicación UIKit construida con el último SDK deberá usar el ciclo de vida UIScene, de lo contrario no se iniciará.

Para adoptar el ciclo de vida UIScene, sigue la guía que corresponda a tu caso de uso:

Migrar a UIScene desplaza la función del AppDelegate: el UISceneDelegate ahora maneja el ciclo de vida de la interfaz de usuario, mientras que el AppDelegate sigue siendo responsable de los eventos del proceso y del ciclo de vida general de la aplicación.

Mueve toda la lógica relacionada con la UI del AppDelegate a los métodos correspondientes del UISceneDelegate. Después de migrar a UIScene, UIKit ya no llamará a los métodos del AppDelegate relacionados con el estado de la UI.

Migrar una aplicación de Flutter

#

Auto-migrar

#

A partir de Flutter 3.41, UIScene está soportado de forma predeterminada. Si tu AppDelegate no ha sido personalizado, la CLI de Flutter migra automáticamente tu aplicación.

Para iniciar la migración, compila o ejecuta tu aplicación con los comandos flutter run o flutter build ios. Si la migración tiene éxito, la CLI mostrará el mensaje Finished migration to UIScene lifecycle y no se requerirá ninguna acción adicional. De lo contrario, la CLI te advertirá y te proporcionará instrucciones para migrar manualmente.

Migrar AppDelegate

#

Anteriormente, los plugins de Flutter se registraban en application:didFinishLaunchingWithOptions:. Para adaptarse a la nueva secuencia de inicio de la aplicación, ahora debes registrar los plugins en un nuevo callback didInitializeImplicitFlutterEngine.

  1. Haz que tu AppDelegate cumpla con el protocolo FlutterImplicitEngineDelegate y mueve el registro de GeneratedPluginRegistrant a 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. Si aplica, crea los method channels y platform views en didInitializeImplicitFlutterEngine.

    Si anteriormente creaste method channels o platform views en application:didFinishLaunchingWithOptions:, mueve esa lógica a 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. Migrar cualquier lógica personalizada dentro de los eventos del ciclo de vida de la aplicación.

    Apple ha deprecado los eventos del ciclo de vida de la aplicación relacionados con el estado de la UI. Después de migrar al ciclo de vida UIScene, UIKit ya no llama a estos eventos.

    Si utilizaste una de estas APIs deprecadas, como applicationDidBecomeActive, probablemente necesites crear un SceneDelegate y migrar a los eventos del ciclo de vida de la escena. Para saber más, consulta la documentación de Apple sobre migración.

    Si implementas tu propio SceneDelegate, debes heredar de FlutterSceneDelegate o cumplir con el protocolo FlutterSceneLifeCycleProvider. Para ver ejemplos, consulta Crear o actualizar un SceneDelegate.

Migrar Info.plist

#

Para completar la migración al ciclo de vida de UIScene, añade una entrada de Application Scene Manifest a tu Info.plist.

Como se muestra en el editor de Xcode:

Editor plist de Xcode para UIApplicationSceneManifest

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

Crear un SceneDelegate (opcional)

#

Si necesitas acceso al SceneDelegate, puedes crear uno heredando de FlutterSceneDelegate:

  1. Abre tu aplicación en Xcode.

  2. Haz clic derecho en la carpeta Runner y luego selecciona New Empty File.

    Opción New Empty File en Xcode

  3. Crea tu clase SceneDelegate.

    Para proyectos de Swift, crea un archivo SceneDelegate.swift:

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

    Para proyectos de Objective-C, crea un archivo SceneDelegate.h y un archivo SceneDelegate.m:

    my_app/ios/Runner/SceneDelegate.h
    objc
    #import <Flutter/Flutter.h>
    #import <UIKit/UIKit.h>
    
    @interface SceneDelegate : FlutterSceneDelegate
    
    @end
    
    my_app/ios/Runner/SceneDelegate.m
    objc
    #import "SceneDelegate.h"
    
    @implementation SceneDelegate
    
    @end
    
  4. En tu archivo Info.plist, cambia el valor de Delegate Class Name (UISceneDelegateClassName) de FlutterSceneDelegate a tu nueva clase.

    Para proyectos de Swift, usa $(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>
    

    Para proyectos de Objective-C, usa 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>
    

Migrar una integración add-to-app

#

De manera similar a FlutterAppDelegate, se recomienda usar FlutterSceneDelegate pero no es requerido. El FlutterSceneDelegate reenvía callbacks de la escena, como openURL, a plugins como local_auth.

Crear o actualizar un SceneDelegate

#
swift
import UIKit
import Flutter

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

Al usar Flutter en una aplicación SwiftUI, opcionalmente puedes usar un FlutterAppDelegate para recibir eventos de la aplicación. Para migrarlo y recibir eventos de UIScene, realiza los siguientes cambios:

  1. Establece el scene delegate como FlutterSceneDelegate en 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. Si tu aplicación no admite múltiples escenas, establece Enable Multiple Scenes en NO en Application Scene Manifest dentro de las propiedades Info de tu target. El soporte para múltiples escenas está habilitado de forma predeterminada para las aplicaciones SwiftUI.

    Editor plist de Xcode para UIApplicationSceneManifest

    Si tu aplicación admite múltiples escenas, consulta Si tu aplicación soporta múltiples escenas para obtener más instrucciones.

Si no puedes heredar de FlutterSceneDelegate

#

Si no puedes heredar de FlutterSceneDelegate, usa el protocolo FlutterSceneLifeCycleProvider y un objeto FlutterPluginSceneLifeCycleDelegate para reenviar eventos del ciclo de vida de la escena a 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];
}

Si tu aplicación soporta múltiples escenas

#

Cuando se habilitan múltiples escenas (UIApplicationSupportsMultipleScenes), Flutter no puede conectar automáticamente un FlutterEngine a su correspondiente UIScene durante la fase inicial de conexión de la escena.

Para garantizar que los plugins de Flutter puedan recibir las opciones iniciales de configuración de la escena (como URLs de enlaces profundos o elementos de acceso directo pasados dentro de la carga útil de UIScene.ConnectionOptions), debes registrar manualmente el FlutterEngine con tu FlutterSceneDelegate o con tu FlutterPluginSceneLifeCycleDelegate dentro del método scene:willConnectToSession:options:.

Si no realizas este registro manual, el FlutterEngine se registrará automáticamente una vez que la vista creada por el FlutterViewController se agregue a la jerarquía de vistas activas. Sin embargo, para ese punto, el motor y sus plugins ya habrán perdido cualquier evento de conexión de inicio pasado durante 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

Si registras manualmente un FlutterEngine con una escena, también debes anular su registro si la vista creada por el FlutterEngine cambia de escena.

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

Migrar un plugin de Flutter

#

No todos los plugins usan eventos de ciclo de vida. Sin embargo, si tu plugin lo hace, mígralo al ciclo de vida basado en escenas de UIKit de la siguiente manera:

  1. Actualiza las versiones del SDK de Dart y Flutter en tu pubspec.yaml.

    Las APIs requeridas para esta migración están disponibles a partir de Flutter 3.38:

    pubspec.yaml
    yaml
    environment:
      sdk: ^3.10.0
      flutter: ">=3.38.0"
    
  2. Adopta el protocolo FlutterSceneLifeCycleDelegate.

    swift
    public final class MyPlugin: NSObject, FlutterPlugin {
    public final class MyPlugin: NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate {
    
    objc
    @interface MyPlugin : NSObject<FlutterPlugin>
    @interface MyPlugin : NSObject<FlutterPlugin, FlutterSceneLifeCycleDelegate>
    
  3. Registra el plugin como un receptor de llamadas de UISceneDelegate.

    Para seguir soportando aplicaciones que aún no se han migrado al ciclo de vida UIScene, considera seguir registrado en el application delegate y mantener también los eventos de AppDelegate.

    swift
    public static func register(with registrar: FlutterPluginRegistrar) {
      ...
      registrar.addApplicationDelegate(instance)
      registrar.addSceneDelegate(instance)
    }
    
    objc
    + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
      ...
      [registrar addApplicationDelegate:instance];
      [registrar addSceneDelegate:instance];
    }
    
  4. Añade los eventos de escena que tu plugin necesite.

    La mayoría de los eventos de UI de AppDelegate tienen un reemplazo uno a uno, como se muestra en la siguiente tabla. Para obtener detalles sobre cada evento, visita la documentación de Apple para UISceneDelegate y UIWindowSceneDelegate.

    Una vez que identifiques los eventos de escena que reemplazan a los eventos de aplicación de los que dependía tu plugin, implementa los métodos correspondientes de FlutterSceneLifeCycleDelegate. Los siguientes fragmentos muestran la firma de cada evento de escena que admite FlutterSceneLifeCycleDelegate; implementa solo los que tu plugin necesite.

    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. Mueve la lógica de inicio de application:willFinishLaunchingWithOptions: y application:didFinishLaunchingWithOptions: a scene:willConnectToSession:options:.

    Aunque application:willFinishLaunchingWithOptions: y application:didFinishLaunchingWithOptions: no están deprecados, sus opciones de inicio son nil después de migrar al ciclo de vida UIScene. Mueve cualquier lógica que dependa de las opciones de inicio al evento scene:willConnectToSession:options:.

  6. Opcional: Migra otras APIs deprecadas para soportar múltiples escenas en el futuro.

    En lugar de acceder a estas APIs, accede a la windowScene a través del viewController, como se muestra en los siguientes ejemplos.

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

Uso personalizado de FlutterViewController

#

Si tu aplicación instancía un FlutterViewController a partir de storyboards en application:didFinishLaunchingWithOptions: por razones distintas a la creación de canales de plataforma, debes adaptarte al nuevo orden de inicialización. Para hacerlo, utiliza una de las siguientes opciones de migración:

  • Crea una subclase de FlutterViewController y coloca la lógica en el método awakeFromNib de la subclase.
  • Especifica un UISceneDelegate en el Info.plist o en el UIApplicationDelegate, y coloca la lógica en scene:willConnectToSession:options:. Para obtener más información, consulta la documentación de Apple.

Ejemplo

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

Ocultar advertencia de migración

#

Para ocultar la advertencia de la CLI de Flutter sobre la migración a UIScene, añade lo siguiente a tu pubspec.yaml:

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

Deshabilitar temporalmente UIScene

#

Para deshabilitar temporalmente el soporte de UIScene, añade un guion bajo (_) delante de Application Scene Manifest en tu Info.plist:

Editor plist de Xcode con un guion bajo delante de Application Scene Manifest

Cuando estés listo para volver a habilitar el soporte de UIScene, elimina el guion bajo.

Timeline

#

Llegó en la versión: 3.38.0-0.1.pre
En la versión estable: 3.38

Apple aún no ha anunciado cuándo impondrá el requisito de UIScene. Una vez que Apple cambie su advertencia por una aserción, las aplicaciones de Flutter que no hayan adoptado el ciclo de vida UIScene fallarán al iniciarse cuando se compilen con el último SDK.

Referencias

#