-
Notifications
You must be signed in to change notification settings - Fork 149
iOS SceneDelegate migration proposal #967
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
artus9033
wants to merge
9
commits into
react-native-community:main
Choose a base branch
from
artus9033:artus9033/scenedelegate
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
e419c63
feat: add SceneDelegate proposal
artus9033 8462aee
fix: front matter
artus9033 2d3189d
feat: formatting of the RFC
artus9033 0b767ce
fix: formatting of code fragments
artus9033 e13ff62
feat: rewording
artus9033 e343215
fix: address comments under RFC
artus9033 35289c7
fix: format note block
artus9033 c0c2f07
chore: iOS27 update
artus9033 2eee0bf
chore: remove duplicate section
artus9033 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| --- | ||
| title: iOS Migration to SceneDelegate | ||
| author: | ||
| - artus9033 | ||
| date: 16.12.2025 | ||
| --- | ||
|
|
||
| # RFC0000: [iOS] Migration to `SceneDelegate` | ||
|
|
||
| ## Summary | ||
|
|
||
| iOS26 introduced deprecation of many UIApplication APIs and made UIScene lifecycle - achieveable with the `SceneDelegate` API - the preferred one, notifying programmers with a warning that "UIScene lifecycle will soon be required". In future versions of iOS, `SceneDelegate` [is going to be the only supported API](https://developer.apple.com/documentation/technotes/tn3187-migrating-to-the-uikit-scene-based-life-cycle#:~:text=Failure%20to%20adopt%20will%20result%20in%20an%20assert%20in%20the%20future.) and therefore we need to start migrating to it. | ||
|
|
||
|  | ||
|
|
||
| iOS27 introduced an assertion that crashes applications built with iOS27 SDK if they are not using the `SceneDelegate`: | ||
| <img width="1162" height="242" alt="image" src="https://github.com/user-attachments/assets/126f06a0-c151-428b-929b-59561210f85b" /> | ||
|
|
||
|
|
||
| This RFC will bring in an additive change that should allow for adoption of a new `SceneDelegate` entrypoint while also allowing for (deprecated) usage of `AppDelegate`. Also, the `@react-native-community/template` and `packages/rn-tester` should be migrated to implement the `SceneDelegate` API. | ||
|
|
||
| > [!NOTE] | ||
| > It is possible to perform dynamic window resizing both via stage manager (split view) and freely via the bottom-right corner handle. `SceneDelegate` with proper configuration in `Info.plist` can enable the multi-window capability for iPadOS, which would require adjustments in React Native code and React Native libraries code to accommodate such design. Such a change would be large and shall be addressed in a separate RFC. The scope of this RFC is to cover a single resizable window (single instance of a given React Native app), yielding the assumption that `UIApplicationSupportsMultipleScenes` **must not** be set to true at the moment. | ||
|
|
||
| ## Basic example | ||
|
|
||
| ``` | ||
| N/A | ||
| ``` | ||
|
|
||
| ## Motivation | ||
|
|
||
| Support for iOS scene lifecycle APIs that are the current preferred approach for apps. The secondary reason is to remediate the problem when the `UIScene` lifecycle will become mandatory in a future release of iOS. | ||
|
|
||
| As an example, the following key APIs are already deprecated: | ||
|
|
||
| - [application:continueUserActivity:restorationHandler:](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/applicationdidbecomeactive\(_:\)?language=objc) in favor of [scene(\_:continue:)](https://developer.apple.com/documentation/UIKit/UI`SceneDelegate`/scene\(_:continue:\)) \- used by RCTLinkingManager | ||
| - [application:openURL:options:](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application\(_:open:options:\)?language=objc) in favor of [scene(\_:openURLContexts:)](https://developer.apple.com/documentation/uikit/ui`scenedelegate`/scene\(_:openurlcontexts:\)) \- used by RCTLinkingManager | ||
| - non-UIScene lifecycle: [https://developer.apple.com/documentation/technotes/tn3187-migrating-to-the-uikit-scene-based-life-cycle](https://developer.apple.com/documentation/technotes/tn3187-migrating-to-the-uikit-scene-based-life-cycle) \- used by base application code | ||
|
|
||
| ## Detailed design | ||
|
|
||
| ### `SceneDelegate` `RCTReactNativeFactory` entrypoint | ||
|
|
||
| To support `SceneDelegate` lifecycle, a new entrypoint for React Native application initialization should be provided in `RCTReactNativeFactory`, which would be invoked from `SceneDelegate` lifecycle methods. The existing entrypoint from `AppDelegate` would be kept for backwards compatibility, making this an additive change. An example code snippet of a ``SceneDelegate`` using React Native proposed in this RFC would be: | ||
|
|
||
| ```objc | ||
| @interface SceneDelegate () | ||
| @end | ||
|
|
||
| @implementation SceneDelegate | ||
|
|
||
| - (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]; | ||
|
|
||
| ReactNativeDelegate *delegate = [[ReactNativeDelegate alloc] init]; | ||
| RCTReactNativeFactory *factory = [[RCTReactNativeFactory alloc] initWithDelegate:delegate]; | ||
|
|
||
| self.reactNativeDelegate = delegate; | ||
| self.reactNativeFactory = factory; | ||
|
|
||
| [factory startReactNativeWithModuleName:@"TestApplication" | ||
| inWindow:self.window | ||
| initialProperties:[self prepareInitialProps] | ||
| connectionOptions:connectionOptions]; | ||
| } | ||
|
|
||
| - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts | ||
| { | ||
| [RCTLinkingManager scene:scene openURLContexts:URLContexts]; | ||
| } | ||
|
|
||
| - (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity | ||
| { | ||
| [RCTLinkingManager scene:scene continueUserActivity:userActivity]; | ||
| } | ||
|
|
||
| @end | ||
| ``` | ||
|
|
||
| ### Migration from `AppDelegate` to `SceneDelegate` | ||
|
|
||
| Adoption of `UIScene` lifecycle requires the following actions: | ||
|
|
||
| * In application base code | ||
| * migration from `AppDelegate` as the primary point of lifecycle-related logic to `SceneDelegate`; for backwards compatibility, React Native's public API integration points will still be compatible with `AppDelegate` approach for users that may not want to migrate immediately | ||
| * invoke React Native `RCTLinkingManager` methods from `SceneDelegate`: | ||
| * `scene:continueUserActivity:` | ||
| * `scene:openURLContexts:` | ||
| * update of the app's Info.plist to include a `UIApplicationSceneManifest` specifying the support and disabling multiple scenes capability | ||
| * In React Native code: | ||
| * migration of code that relies on `launchOptions` and deprecated `UIApplicationLaunchOptions`\* keys to `UIScene` lifecycle and `UIScene.ConnectionOptions.userActivities` | ||
| * In React Native code, React Native native libraries’ code: | ||
| * migration of app lifecycle methods from application\* to scene\* as per [https://developer.apple.com/documentation/technotes/tn3187-migrating-to-the-uikit-scene-based-life-cycle](https://developer.apple.com/documentation/technotes/tn3187-migrating-to-the-uikit-scene-based-life-cycle) | ||
| * migration of code that relies on *any* *deprecated* `AppDelegate`-related APIs, which detailed description of is presented below | ||
|
|
||
| To enforce the user not to enable the `UIApplicationSceneManifest`.`UIApplicationSupportsMultipleScenes` capability in `Info.plist`, from now on, the following warning would be printed from `RCTReactNativeFactory` provided the user had enabled the multi-window capability: | ||
|
|
||
|  | ||
|
|
||
| #### Migration: React Native app template, RNTester | ||
|
|
||
| In case of the React Native app template, the iOS boilerplate code is limited only to basic bootstrapping of the application. This implies adjustments to: | ||
|
|
||
| * `Info.plist` \- add support for `SceneDelegate` | ||
| * `SceneDelegate.mm` \- implement the `SceneDelegate` | ||
| * `AppDelegate.mm` \- move app bootstrap code from here to `SceneDelegate.mm` | ||
|
|
||
| #### Migration: inspect usages of `RCTReactNativeFactory` | ||
|
|
||
| Support methods for initializing React Native from `SceneDelegate`’s lifecycle methods. | ||
|
|
||
| #### Migration: `RCTLinkingManager` | ||
|
|
||
| The linking manager is using `AppDelegate` methods for handling URLs being opened at runtime. This needs to be migrated to `SceneDelegate`. To maintain backwards compatibility, we can implement both approaches and \- to ensure that only one is invoked at a given time \- conditionally check if the app is based on `AppDelegate` or has scenes to ensure only one listener handles the event. The Scene lifecycle options (`NSDictionary`) are adapted to the format of `AppDelegate` launchOptions (`NSDictionary` as well). | ||
|
|
||
| #### Migration: RCTDevLoadingView | ||
|
|
||
| The current implementation does not account for updating the overlay `UIWindow` constraints so it does not get resized along with the main window. The current implementation mixes automatic layout constraints and manual manipulation of `frame` property of the containing `UIWindow`. The proposed solution is to fully rely on Auto Layout constraints to position and size the loading view overlay window and instead of animating the window's frame, animate the container view's `transform`. | ||
|
|
||
|  | ||
|  | ||
|
|
||
| ## Drawbacks | ||
|
|
||
| The drawback is that users will finally need to migrate to the `SceneDelegate` lifecycle and use the newly-introduced React Native entrypoint API. | ||
|
|
||
| ## Alternatives | ||
|
|
||
| No alternatives have been considered. Apple will enforce the change and we have to comply with it. | ||
| An alternative way to approach it is to implement some breaking changes without preserving backward compatibility for a while. This can backfire as our users could churn away from React Native. | ||
|
|
||
| ## Adoption strategy | ||
|
|
||
| This would be a breaking change for apps referencing `AppDelegate`-related APIs (such as lifecycle / obtaining the window instance) or using libraries that do so. For users that do not use the aforementioned, this change would not be breaking. Third-party libraries that made use of `AppDelegate`-related APIs should migrate to UIKit scenes-related APIs to work properly with multiple scenes. | ||
|
|
||
| An action plan for this RFC is as follows: | ||
| - add React Native UIScene lifecycle API, migrate internal modules and RNTester $\rightarrow$ will be covered by the PR following this RFC | ||
| - migrate user React Native applications: according to the upgrade helper diff $\rightarrow$ the scope of implementing this RFC is updating the React Native app template | ||
| - migrate user React Native applications that have custom native code to use UIScene-lifecycle APIs $\rightarrow$ the scope of implementing this RFC is updating the React Native app template and React Native documentation | ||
| - we should explicitly mention APIs referenced in React Native code that were used with `AppDelegate` and were migrated in React Native internal code as examples: | ||
| - `UIScreen.mainScreen.bounds.size` -> `RCTKeyWindow().bounds.frame.size` | ||
| - `RCTSharedApplicatiorean.delegate.window` -> `RCTKeyWindow()` | ||
|
|
||
| From the users' perspective, for use cases not featuring native code, the migration should follow the React Native upgrade helper diffs to adapt native app code. For use cases featuring custom native code, the migration should follow the React Native upgrade helper diffs and also migrate to UIScene lifecycle APIs. | ||
|
|
||
| ## How we teach this | ||
|
|
||
| As described above: | ||
|
|
||
| 1. Via React Native documentation | ||
| 2. Via React Native upgrade helper diffs | ||
| 3. Via example in React Native code, RNTester app and the community app template | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think it would be ok to not have that backwards compatibility. The Delegate is on a clock anyway that we do not control, so everyone NEEDS to migrate, and keeping the old approach around is just delaying an inevitable that will be significantly more painful then and we would need to live the double life in the mean time supporting both approaches.
Might be best to just say, from this version of RN, in order to be compliant with Apple's requirements, we are moving off, older versions not affected. Just being clear in the comms with the framework end users
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree on the point that this has to be removed at some stage, yet the objective I had in mind was to bring an additive, non-breaking change. We should definitely deprecate those methods, but a sudden removal would make an unpleasant surprise for the users. By deprecating, we make it a gentle migration and we'll get to removal anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with Parsa. It is better to do this properly once rather than carrying things forward just for backward compatibility. Many things in the core break over time without being visible to us, and this is not an exception.
Another issue is the terminology itself. In the React Native ecosystem, the term “users” is often used to refer to consumers of a package, which easily causes confusion. In our case, those “users” are developers, not end users. Since the audience is developers, they are expected to adapt to changes and learn new patterns. We are not building a product for non-technical end customers.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sharing that, then I think the matter is up to be discussed. WDYT @cipolleschi ?
Regarding the "users", definitely the user persona I was referring to in this context was the developer - yet, I'll keep in mind that the default meaning here is the end user, thank you for noting that!