Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions proposals/0000-ios-scenedelegate-migration.md
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.

![](assets/0000-ios-deprecation-warning.png)

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

Copy link
Copy Markdown

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

@artus9033 artus9033 Dec 19, 2025

Copy link
Copy Markdown
Author

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.

Copy link
Copy Markdown

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.

@artus9033 artus9033 Dec 19, 2025

Copy link
Copy Markdown
Author

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!

* 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:

![](assets/0000-console-warning-unsupported-configuration.png)

#### 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`.

![](assets/0000-RCTDevLoadingView-width.png)
![](assets/0000-RCTDevLoadingView-width-clipped.png)

## 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.
Binary file added proposals/assets/0000-ios-deprecation-warning.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.