Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
917f539
feat: migrate to SceneDelegate, update RNTester code
artus9033 Sep 10, 2025
8b4adc0
feat: provide two configurations for launching RNTester based on Scen…
artus9033 Sep 10, 2025
2863c4c
feat: migrate RNTester NativeExampleViews to support SceneDelegate
artus9033 Sep 10, 2025
d27e491
chore: remove obsolete code
artus9033 Sep 10, 2025
0ac5467
chore: updated Podfile.lock
artus9033 Sep 10, 2025
0232d98
chore: revert unnecessary migration in RCTDeviceInfo
artus9033 Sep 10, 2025
a81e935
fix: migrate usages of application.delegate.window to use RCTKeyWindo…
artus9033 Sep 10, 2025
6a0dce2
feat: add NativeDeviceInfo.getInfo, deprecate NativeDeviceInfo.getCon…
artus9033 Sep 11, 2025
641a29c
test: test if getInfo works and has Dimensions property
artus9033 Sep 11, 2025
d961af6
feat: added imperative non-subscription demos to DimensionsExample in…
artus9033 Sep 11, 2025
dc7763c
feat: stub for DeviceInfoModule.getInfo CXX implementation
artus9033 Sep 11, 2025
c0bc778
fix: RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD RCTDeviceInfo.getInfo
artus9033 Sep 11, 2025
c77f68a
fix(test): fix integration tests js app problems due to ES exports
artus9033 Sep 11, 2025
1e7a30e
fix(test): fix unit tests compilation errors
artus9033 Sep 11, 2025
5448f10
fix: ensure that RCTDeviceInfo::invalidateCachedInfo logic is run on …
artus9033 Sep 11, 2025
7eebb2f
fix(test): add jest mock for JS DeviceInfo.getInfo
artus9033 Sep 11, 2025
1f25576
fix(test): fix AccessibilityManagerTest failing because of handling l…
artus9033 Sep 11, 2025
d436cd8
refactor: explicitly depend on retained self in RCTExecuteOnMainQueue…
artus9033 Sep 12, 2025
75d2c66
refactor: typo in TODO comment in RCTDeviceInfo
artus9033 Sep 12, 2025
5045ad8
test: disable AccessibilityManagerTest due to T225745315
artus9033 Sep 12, 2025
cb9a5b4
docs: update unit and integration tests instructions
artus9033 Sep 13, 2025
697758b
Merge branch 'main' into artur/feat/scene-delegate
artus9033 Sep 13, 2025
b37bcd9
chore: update Podfile.lock
artus9033 Sep 13, 2025
de29fca
Revert "fix: RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD RCTDeviceInfo.get…
artus9033 Sep 18, 2025
2ed5739
Revert "feat: stub for DeviceInfoModule.getInfo CXX implementation"
artus9033 Sep 18, 2025
4ede7e8
Revert "fix(test): add jest mock for JS DeviceInfo.getInfo"
artus9033 Sep 18, 2025
7bc2b30
Revert "feat: add NativeDeviceInfo.getInfo, deprecate NativeDeviceInf…
artus9033 Sep 18, 2025
d3c2cd4
feat: invalidate cached value of dimensions for internal state consis…
artus9033 Sep 18, 2025
7db2384
chore: updated RNTester Podfile, integration tests xcscheme and pbxproj
artus9033 Sep 18, 2025
a4503ab
feat: deprecate getConstants in NativeDeviceInfo, introduce getInfo a…
artus9033 Sep 18, 2025
8771ff1
chore: update Podfile
artus9033 Sep 18, 2025
4268e4c
Merge branch 'main' into artur/feat/scene-delegate
artus9033 Sep 21, 2025
785f869
refactor: sort imports in DimensionsExample
artus9033 Sep 21, 2025
e332eb7
feat: added NativeDeviceInfo.getInfo API to RN API snapshot
artus9033 Sep 21, 2025
c69f974
fix: typechecking in IntegrationTestsApp.js
artus9033 Sep 21, 2025
3b0416b
feat: warn at runtime from RCTReactNativeFactory if unsupported capab…
artus9033 Sep 21, 2025
543c296
chore: update Podfile.lock
artus9033 Sep 21, 2025
6f5935a
Merge branch 'main' into artur/feat/scene-delegate
artus9033 Sep 21, 2025
3f96863
Merge branch 'main' into artur/feat/scene-delegate
artus9033 Sep 29, 2025
a72e1e3
wip: PoC fixing delayed application of ShadowTree commits after inten…
artus9033 Nov 5, 2025
f661e41
Merge remote-tracking branch 'facebook/main' into artur/feat/scene-de…
artus9033 Nov 6, 2025
5aac7cd
chore: retract the workarounds
artus9033 Nov 19, 2025
1c0c5ad
Merge branch 'main' into feat/scene-delegate
artus9033 Nov 20, 2025
2d64c04
chore: update ReactNativeApi.d.ts SignedSource
artus9033 Nov 20, 2025
ea3c55e
chore: use RCTLogWarn in RCTReactNativeFactory if running with multiw…
artus9033 Nov 20, 2025
693b31d
feat: support RCTDevLoadingView resizing
artus9033 Dec 1, 2025
b822bec
fix: print warning of unsupported configuration from hostDidStart to …
artus9033 Dec 18, 2025
d751f4c
Merge branch 'main' into feat/scene-delegate
artus9033 Dec 18, 2025
041c9bc
chore: remove new NativeDeviceInfo API
artus9033 Dec 18, 2025
a75ed5b
chore: update project.pbxproj
artus9033 Dec 18, 2025
37cfa12
feat: support resizing in RCTDevLoadingView using automatic constrain…
artus9033 Dec 18, 2025
dbad6af
Merge branch 'main' into feat/scene-delegate
artus9033 Jun 25, 2026
d874dda
feat: add RCTSceneDelegate for UIScene-based iOS app bootstrap
artus9033 Jul 9, 2026
a0b8944
feat: migrate RNTester and HelloWorld to RCTSceneDelegate
artus9033 Jul 9, 2026
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
7 changes: 4 additions & 3 deletions packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
NS_ASSUME_NONNULL_BEGIN

/**
* @deprecated RCTAppDelegate is deprecated and will be removed in a future version of React Native. Use
`RCTReactNativeFactory` instead.
* @deprecated RCTAppDelegate is deprecated and will be removed in a future version of React Native. For new apps
* using the UIScene lifecycle, prefer `RCTSceneDelegate` or `RCTReactNativeFactory` with a SceneDelegate entrypoint.
* For AppDelegate-only apps, use `RCTReactNativeFactory` directly.
*
* The RCTAppDelegate is an utility class that implements some base configurations for all the React Native apps.
* It is not mandatory to use it, but it could simplify your AppDelegate code.
Expand Down Expand Up @@ -55,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN
* - (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
*/
__attribute__((deprecated(
"RCTAppDelegate is deprecated and will be removed in a future version of React Native. Use `RCTReactNativeFactory` instead.")))
"RCTAppDelegate is deprecated and will be removed in a future version of React Native. For UIScene apps use `RCTSceneDelegate`; otherwise use `RCTReactNativeFactory`.")))
@interface RCTAppDelegate : RCTDefaultReactNativeFactoryDelegate<UIApplicationDelegate>

/// The window object, used to render the UViewControllers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ - (RCTColorSpace)defaultColorSpace

- (NSURL *_Nullable)bundleURL
{
[NSException raise:@"RCTAppDelegate::bundleURL not implemented"
[NSException raise:@"RCTReactNativeFactoryDelegate::bundleURL not implemented"
format:@"Subclasses must implement a valid getBundleURL method"];
return nullptr;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable };

@interface RCTReactNativeFactory : NSObject

/**
* Bootstrap entrypoints:
* - **AppDelegate path**: `startReactNativeWithModuleName:inWindow:launchOptions:` — call from
* `application:didFinishLaunchingWithOptions:` or `RCTAppDelegate`.
* - **SceneDelegate path**: `startReactNativeWithModuleName:inWindow:connectionOptions:` — call from
* `scene:willConnectToSession:options:` or `RCTSceneDelegate`.
*/

- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate;

- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate releaseLevel:(RCTReleaseLevel)releaseLevel;
Expand All @@ -73,6 +81,32 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable };
initialProperties:(NSDictionary *_Nullable)initialProperties
launchOptions:(NSDictionary *_Nullable)launchOptions;

/// This is a SceneDelegate entrypoint method to start a React Native instance with the specified module name, window
/// and connection options for linking & user activity information. As it's usual for the typical deep-linking use case,
/// only the first item in URLContexts from connectionOptions will be checked; the same applies to userActivities.
/// @param moduleName name of the JS module to load
/// @param window the window to launch in
/// @param connectionOptions the scene's connection options
- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
connectionOptions:(UISceneConnectionOptions *_Nullable)connectionOptions;

/// This is a SceneDelegate entrypoint method to start a React Native instance with the specified module name, window
/// and connection options for linking, initial properties & user activity information. As it's usual for the typical
/// deep-linking use case, only the first item in URLContexts from connectionOptions will be checked; the same applies
/// to userActivities.
/// @param moduleName name of the JS module to load
/// @param window the window to launch in
/// @param initialProperties the initial root properties
/// @param connectionOptions the scene's connection options
- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
initialProperties:(NSDictionary *_Nullable)initialProperties
connectionOptions:(UISceneConnectionOptions *_Nullable)connectionOptions;

@property (nonatomic, nullable) RCTBridge *bridge
__attribute__((deprecated("The bridge is deprecated and will be removed when removing the legacy architecture.")));

#if !defined(RCT_REMOVE_LEGACY_ARCH)
@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__((
deprecated("The bridgeAdapter is deprecated and will be removed when removing the legacy architecture.")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ @interface RCTReactNativeFactory () <
RCTHostDelegate,
RCTJSRuntimeConfiguratorProtocol,
RCTTurboModuleManagerDelegate>

/// Adapter for SceneDelegate entrypoint's UISceneConnectionOptions that converts it to the AppDelegate-style
/// NSDictionary for internal RN needs
/// @param connectionOptions the scene's connection options
/// @return an NSDictionary with proper UIApplicationLaunchOptions- keys set to values from connectionOptions
- (NSDictionary *)convertConnectionOptionsToLaunchOptions:(UISceneConnectionOptions *)connectionOptions;

@end

@implementation RCTReactNativeFactory
Expand Down Expand Up @@ -95,6 +102,58 @@ - (void)startReactNativeWithModuleName:(NSString *)moduleName
[window makeKeyAndVisible];
}

#pragma mark - UIScene.ConnectionOptions

- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
connectionOptions:(UISceneConnectionOptions *_Nullable)connectionOptions
{
[self startReactNativeWithModuleName:moduleName
inWindow:window
initialProperties:nil
launchOptions:[self convertConnectionOptionsToLaunchOptions:connectionOptions]];
}

- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
initialProperties:(NSDictionary *_Nullable)initialProperties
connectionOptions:(UISceneConnectionOptions *_Nullable)connectionOptions
{
[self startReactNativeWithModuleName:moduleName
inWindow:window
initialProperties:initialProperties
launchOptions:[self convertConnectionOptionsToLaunchOptions:connectionOptions]];
}

- (NSDictionary *)convertConnectionOptionsToLaunchOptions:(UISceneConnectionOptions *)connectionOptions
{
NSMutableDictionary *launchOptions = [NSMutableDictionary dictionary];

// handle launch URL
if (connectionOptions.URLContexts.count > 0) {
UIOpenURLContext *urlContext = connectionOptions.URLContexts.allObjects.firstObject;

if (urlContext.URL) {
launchOptions[UIApplicationLaunchOptionsURLKey] = urlContext.URL;
}
}

// handle user activities
if (connectionOptions.userActivities.count > 0) {
NSUserActivity *activity = connectionOptions.userActivities.allObjects.firstObject;

if (activity) {
NSMutableDictionary *userActivityDict = [NSMutableDictionary dictionary];
userActivityDict[UIApplicationLaunchOptionsUserActivityTypeKey] = activity.activityType;
userActivityDict[@"UIApplicationLaunchOptionsUserActivityKey"] = activity;

launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey] = userActivityDict;
}
}

return launchOptions;
}

#pragma mark - RCTUIConfiguratorProtocol

- (RCTColorSpace)defaultColorSpace
Expand Down Expand Up @@ -209,6 +268,18 @@ - (void)hostDidStart:(RCTHost *)host
{
if ([_delegate respondsToSelector:@selector(hostDidStart:)]) {
[_delegate hostDidStart:host];

// check if the application is running with multiple scenes capability enabled in scene manifest (Info.plist),
// which is unsupported by RN at the moment, and warn in such case
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
NSDictionary *sceneManifest = infoDict ? infoDict[@"UIApplicationSceneManifest"] : nil;
BOOL supportsMultipleScenes =
sceneManifest ? [sceneManifest[@"UIApplicationSupportsMultipleScenes"] boolValue] : false;

if (supportsMultipleScenes) {
RCTLogWarn(
@"RCTReactNativeFactory: (WARNING - UNSUPPORTED RN APP CONFIGURATION) Your application is running with the Info.plist UIApplicationSceneManifest.UIApplicationSupportsMultipleScenes key set to true, which is NOT supported by React Native at the moment. Allowing the user to run multiple windows of a RN application means allowing to run multiple instances of React Native and libraries in the same process, which may cause ALL SORTS OF PROBLEMS in implementations which use singletons or static storage lifetime variables.");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#import <React/RCTRootView.h>
#import <React/RCTUtils.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
#import "RCTAppDelegate.h"
#import "RCTAppSetupUtils.h"

#if RN_DISABLE_OSS_PLUGIN_HEADER
Expand Down
104 changes: 104 additions & 0 deletions packages/react-native/Libraries/AppDelegate/RCTSceneDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import <UIKit/UIKit.h>
#import "RCTDefaultReactNativeFactoryDelegate.h"
#import "RCTReactNativeFactory.h"
#import "RCTRootViewFactory.h"

@protocol RCTBridgeDelegate;
@protocol RCTComponentViewProtocol;
@class RCTRootView;
@class RCTSurfacePresenterBridgeAdapter;
@protocol RCTDependencyProvider;

NS_ASSUME_NONNULL_BEGIN

/**
* Optional utility base class for SceneDelegate-based React Native apps using the UIScene lifecycle.
*
* For AppDelegate-only apps, use `RCTAppDelegate` or `RCTReactNativeFactory` directly.
*
* Usage:
* 1. Declare `UIApplicationSceneManifest` in Info.plist with your SceneDelegate class.
* 2. Subclass `RCTSceneDelegate` and configure it before calling `[super ...]` in
* `scene:willConnectToSession:options:`.
*
* ```objc
* #import <React_RCTAppDelegate/RCTSceneDelegate.h>
*
* @implementation SceneDelegate
*
* - (void)scene:(UIScene *)scene
* willConnectToSession:(UISceneSession *)session
* options:(UISceneConnectionOptions *)connectionOptions
* {
* self.moduleName = @"MyApp"; // required: JS module name registered in AppRegistry
* self.initialProps = @{
* // optional root props
* };
* self.dependencyProvider = [[RCTAppDependencyProvider alloc] init]; // if using codegen
* [super scene:scene willConnectToSession:session options:connectionOptions];
* }
*
* - (NSURL *)bundleURL
* {
* return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
* }
*
* @end
* ```
*
* Required configuration (set before `[super scene:willConnectToSession:options:]`):
* - `moduleName` — the AppRegistry component name to mount.
* - `bundleURL` — override to return the JS bundle URL (raises if not implemented).
*
* Optional configuration:
* - `initialProps` — props passed to the root component.
* - `dependencyProvider` — codegen module/component provider.
* - `automaticallyLoadReactNativeWindow` — defaults to `YES`; set to `NO` to call
* `loadReactNativeWindow:` yourself after custom setup.
*
* Linking is forwarded automatically via `RCTLinkingManager`. Push notifications and other
* `UIApplicationDelegate` callbacks should remain on your AppDelegate.
*
* Overridable methods (inherited from `RCTDefaultReactNativeFactoryDelegate`):
* - (UIViewController *)createRootViewController;
* - (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController;
* - (void)customizeRootView:(RCTRootView *)rootView;
* - (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents;
* - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
* jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker;
*/
@interface RCTSceneDelegate : RCTDefaultReactNativeFactoryDelegate <UIWindowSceneDelegate>

/// The window object used to render UIViewControllers for this scene.
@property (nonatomic, strong, nullable) UIWindow *window;

#if !defined(RCT_REMOVE_LEGACY_ARCH)
@property (nonatomic, nullable) RCTBridge *bridge
__attribute__((deprecated("The bridge is deprecated and will be removed when removing the legacy architecture.")));
@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__((
deprecated("The bridge adapter is deprecated and will be removed when removing the legacy architecture.")));
#endif

@property (nonatomic, strong, nullable) NSString *moduleName;
@property (nonatomic, strong, nullable) NSDictionary *initialProps;
@property (nonatomic, strong, nullable) RCTReactNativeFactory *reactNativeFactory;

/// If `automaticallyLoadReactNativeWindow` is set to `true`, the React Native window is loaded in
/// `scene:willConnectToSession:options:`.
@property (nonatomic, assign) BOOL automaticallyLoadReactNativeWindow;

- (RCTRootViewFactory *)rootViewFactory;

/// Loads the React Native root view into `self.window` using UIScene connection options.
- (void)loadReactNativeWindow:(UISceneConnectionOptions *_Nullable)connectionOptions;

@end

NS_ASSUME_NONNULL_END
68 changes: 68 additions & 0 deletions packages/react-native/Libraries/AppDelegate/RCTSceneDelegate.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import "RCTSceneDelegate.h"

#import <React/RCTLinkingManager.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#import <React/RCTUtils.h>

@implementation RCTSceneDelegate

- (instancetype)init
{
if (self = [super init]) {
_automaticallyLoadReactNativeWindow = YES;
}
return self;
}

- (void)scene:(UIScene *)scene
willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions
{
if (![scene isKindOfClass:[UIWindowScene class]]) {
return;
}

self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self];

UIWindowScene *windowScene = (UIWindowScene *)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];

if (self.automaticallyLoadReactNativeWindow) {
[self loadReactNativeWindow:connectionOptions];
}
}

- (void)loadReactNativeWindow:(UISceneConnectionOptions *)connectionOptions
{
[self.reactNativeFactory startReactNativeWithModuleName:self.moduleName
inWindow:self.window
initialProperties:self.initialProps
connectionOptions:connectionOptions];
}

- (RCTRootViewFactory *)rootViewFactory
{
return self.reactNativeFactory.rootViewFactory;
}

#pragma mark - Linking (SceneDelegate lifecycle)

- (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
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ NS_ASSUME_NONNULL_BEGIN

/**
* This method can be used to customize the rootView that is passed to React Native.
* A typical example is to override this method in the AppDelegate to change the background color.
* To achieve this, add in your `AppDelegate.mm`:
* Override on your `RCTReactNativeFactoryDelegate` (e.g. in AppDelegate, SceneDelegate factory delegate, or
* `RCTAppDelegate` / `RCTSceneDelegate` subclass). Example:
* ```
* - (void)customizeRootView:(RCTRootView *)rootView
* {
Expand Down
Loading