diff --git a/sites/docs/src/content/release/breaking-changes/uiscenedelegate.md b/sites/docs/src/content/release/breaking-changes/uiscenedelegate.md index 39e5f629b8..b15003cd9d 100644 --- a/sites/docs/src/content/release/breaking-changes/uiscenedelegate.md +++ b/sites/docs/src/content/release/breaking-changes/uiscenedelegate.md @@ -1,8 +1,8 @@ --- -title: UISceneDelegate adoption +title: UIScene adoption description: >- Learn how to migrate your Flutter iOS app, add-to-app integration, or plugin - to Apple's required UIScene lifecycle using FlutterSceneDelegate. + to Apple's required UIScene lifecycle. --- {% render "docs/breaking-changes.md" %} @@ -595,143 +595,8 @@ forward scene lifecycle events to Flutter. ### 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:`. - - - - -```swift title="SceneDelegate.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) - } -} -``` - - - - -```objc title="SceneDelegate.h" -#import -#import -#import - -@interface SceneDelegate : FlutterSceneDelegate -@property(nonatomic, strong) FlutterEngine *flutterEngine; -@end -``` - -```objc title="SceneDelegate.m" -#import "SceneDelegate.h" -#import "ViewController.h" - -@implementation SceneDelegate - -- (instancetype)init { - if (self = [super init]) { - _flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"]; - } - return self; -} - -- (void)scene:(UIScene *)scene - willConnectToSession:(UISceneSession *)session - options:(UISceneConnectionOptions *)connectionOptions { - if (![scene isKindOfClass:[UIWindowScene class]]) { - return; - } - UIWindowScene *windowScene = (UIWindowScene *)scene; - self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; - - [self.flutterEngine run]; - [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; - - // If using FlutterSceneDelegate: - [self registerSceneLifeCycleWithFlutterEngine:self.flutterEngine]; - - // If using FlutterSceneLifeCycleProvider: - // [self.sceneLifeCycleDelegate registerSceneLifeCycleWithFlutterEngine:self.flutterEngine]; - - ViewController *viewController = [[ViewController alloc] initWithEngine:self.flutterEngine]; - self.window.rootViewController = viewController; - [self.window makeKeyAndVisible]; - [super scene:scene willConnectToSession:session options:connectionOptions]; -} - -@end -``` - - - - -If you manually register a `FlutterEngine` with a scene, -you must also unregister it if the view -created by the `FlutterEngine` changes scenes. - - - - -```swift -// If using FlutterSceneDelegate: -self.unregisterSceneLifeCycle(with: flutterEngine) - -// If using FlutterSceneLifeCycleProvider: -sceneLifeCycleDelegate.unregisterSceneLifeCycle(with: flutterEngine) -``` - - - - -```objc -// If using FlutterSceneDelegate: -[self unregisterSceneLifeCycleWithFlutterEngine:self.flutterEngine]; - -// If using FlutterSceneLifeCycleProvider: -[self.sceneLifeCycleDelegate unregisterSceneLifeCycleWithFlutterEngine:self.flutterEngine]; -``` - - - +Flutter doesn't fully support multiple scenes yet, +but support is in progress. @@ -821,7 +686,6 @@ migrate it to UIKit's scene-based lifecycle as follows: | [`application:continueUserActivity:restorationHandler:`][] | [`scene:continueUserActivity:`][] | | [`application:performActionForShortcutItem:completionHandler:`][] | [`windowScene:performActionForShortcutItem:completionHandler:`][] | | [`application:openURL:options:`][] | [`scene:openURLContexts:`][] | - | [`application:performFetchWithCompletionHandler:`][] | [`BGAppRefreshTask`][] | | [`application:willFinishLaunchingWithOptions:`][] | [`scene:willConnectToSession:options:`][] | | [`application:didFinishLaunchingWithOptions:`][] | [`scene:willConnectToSession:options:`][] | @@ -911,6 +775,120 @@ migrate it to UIKit's scene-based lifecycle as follows: Move any logic that relies on the launch options to the `scene:willConnectToSession:options:` event. + For apps that adopt `UIScene`, Flutter calls + `application:willFinishLaunchingWithOptions:` and + `application:didFinishLaunchingWithOptions:` during the + `scene:willConnectToSession:options:` callback. + However, some Apple APIs must be called before + `application:didFinishLaunchingWithOptions:` returns. + To work around this restriction, plugins can expose an API that + lets you register background tasks directly in the `AppDelegate`'s + `application:willFinishLaunchingWithOptions:` method. + + The following Apple APIs must be configured before app launch finishes: + + - [`BGTaskScheduler.registerForTaskWithIdentifier:usingQueue:launchHandler:`][] + + > Registration of all launch handlers must be complete + > before the end of `applicationDidFinishLaunching:`. + + - [`UNUserNotificationCenterDelegate`][] + + > You must assign your delegate object to the `UNUserNotificationCenter` + > object before your app finishes launching. + > For example, in an iOS app, you must assign it in the + > `application:willFinishLaunchingWithOptions:` or + > `application:didFinishLaunchingWithOptions:` + > method of your app delegate. + > Assigning a delegate after the system calls these methods + > might cause you to miss incoming notifications. + + - [`HKHealthStore.enableBackgroundDeliveryForType:frequency:withCompletion:`][] + + > If you plan on supporting background delivery, + > set up all your observer queries in your app delegate’s + > `application:didFinishLaunchingWithOptions:` method. + > By setting up the queries in + > `application:didFinishLaunchingWithOptions:`, + > you ensure that you’ve instantiated your queries, + > and they’re ready to use before HealthKit delivers the updates. + + For example, to support `BGTaskScheduler`: + + + + ```swift diff + 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(identifer: 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 + - } + } + ``` + + + + + ```objc diff + @interface BGTaskPlugin : NSObject + + + (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 *)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 + ``` + + + + 1. Optional: Migrate other deprecated APIs to support multiple scenes in the future. @@ -1022,7 +1000,6 @@ migrate it to UIKit's scene-based lifecycle as follows: [`application:continueUserActivity:restorationHandler:`]: {{site.apple-dev}}/documentation/uikit/uiapplicationdelegate/1623072-application [`application:performActionForShortcutItem:completionHandler:`]: {{site.apple-dev}}/documentation/uikit/uiapplicationdelegate/application(_:performactionfor:completionhandler:) [`application:openURL:options:`]: {{site.apple-dev}}/documentation/uikit/uiapplicationdelegate/1623112-application -[`application:performFetchWithCompletionHandler:`]: {{site.apple-dev}}/documentation/uikit/uiapplicationdelegate/1623125-application [`application:willFinishLaunchingWithOptions:`]: {{site.apple-dev}}/documentation/uikit/uiapplicationdelegate/1623032-application [`application:didFinishLaunchingWithOptions:`]: {{site.apple-dev}}/documentation/uikit/uiapplicationdelegate/1622921-application [`sceneDidBecomeActive`]: {{site.apple-dev}}/documentation/uikit/uiscenedelegate/3197915-scenedidbecomeactive @@ -1032,7 +1009,6 @@ migrate it to UIKit's scene-based lifecycle as follows: [`scene:continueUserActivity:`]: {{site.apple-dev}}/documentation/uikit/uiscenedelegate/scene(_:continue:) [`windowScene:performActionForShortcutItem:completionHandler:`]: {{site.apple-dev}}/documentation/uikit/uiwindowscenedelegate/3238088-windowscene [`scene:openURLContexts:`]: {{site.apple-dev}}/documentation/uikit/uiscenedelegate/3238059-scene -[`BGAppRefreshTask`]: {{site.apple-dev}}/documentation/backgroundtasks/bgapprefreshtask [`scene:willConnectToSession:options:`]: {{site.apple-dev}}/documentation/uikit/uiscenedelegate/3197914-scene [`UIScreen mainScreen`]: {{site.apple-dev}}/documentation/uikit/uiscreen/1617815-mainscreen [`UIWindowScene screen`]: {{site.apple-dev}}/documentation/uikit/uiwindowscene/screen?language=objc @@ -1042,6 +1018,9 @@ migrate it to UIKit's scene-based lifecycle as follows: [`UIWindowScene windows`]: {{site.apple-dev}}/documentation/uikit/uiwindowscene/windows?language=objc [`UIApplicationDelegate window`]: {{site.apple-dev}}/documentation/uikit/uiapplicationdelegate/window [`UIView window`]: {{site.apple-dev}}/documentation/uikit/uiview/window?language=objc +[`BGTaskScheduler.registerForTaskWithIdentifier:usingQueue:launchHandler:`]: {{site.apple-dev}}/documentation/backgroundtasks/bgtaskscheduler/register(fortaskwithidentifier:using:launchhandler:)?language=objc +[`UNUserNotificationCenterDelegate`]: {{site.apple-dev}}/documentation/usernotifications/unusernotificationcenterdelegate?language=objc +[`HKHealthStore.enableBackgroundDeliveryForType:frequency:withCompletion:`]: {{site.apple-dev}}/documentation/healthkit/hkhealthstore/enablebackgrounddelivery(for:frequency:withcompletion:)?language=objc ## Bespoke FlutterViewController usage