Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,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.")));

@property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory;

@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__((
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,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 @@ -231,6 +290,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
20 changes: 20 additions & 0 deletions packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,37 @@

@interface RCTLinkingManager : RCTEventEmitter

#pragma mark - AppDelegate methods

/// Lifecycle method informing of a URL being opened with the app, must be invoked from the AppDelegate.
/// Must be invoked from the AppDelegate.
/// Note: this is an implementation using the iOS 9.0-26.0 API
+ (BOOL)application:(nonnull UIApplication *)app
openURL:(nonnull NSURL *)URL
options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;

/// Lifecycle method handling a URL being opened with the app, must be invoked from the AppDelegate.
/// Must be invoked from the AppDelegate.
/// Note: this is an implementation using the iOS 4.2-9.0 API
+ (BOOL)application:(nonnull UIApplication *)application
openURL:(nonnull NSURL *)URL
sourceApplication:(nullable NSString *)sourceApplication
annotation:(nonnull id)annotation;

/// Lifecycle method handling user activity being performed.
/// Must be invoked from the AppDelegate.
+ (BOOL)application:(nonnull UIApplication *)application
continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler;

#pragma mark - SceneDelegate methods

/// Successor to AppDelegate's application:continueUserActivity:restorationHandler:, which handles user activity being
/// performed. Must be invoked from the SceneDelegate.
+ (void)scene:(nonnull UIScene *)scene continueUserActivity:(nonnull NSUserActivity *)userActivity;

/// Successor to AppDelegate's application:openURL:options:, which handles user activity being performed.
/// Must be invoked from the SceneDelegate.
+ (void)scene:(nonnull UIScene *)scene openURLContexts:(nonnull NSSet<UIOpenURLContext *> *)URLContexts;

@end
78 changes: 64 additions & 14 deletions packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,38 @@

static NSString *const kOpenURLNotification = @"RCTOpenURLNotification";

static void postNotificationWithURL(NSURL *URL, id sender)
{
NSDictionary<NSString *, id> *payload = @{@"url" : URL.absoluteString};
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification object:sender userInfo:payload];
}

@interface RCTLinkingManager () <NativeLinkingManagerSpec>

/// Common logic for handling user activities originating from both AppDelegate- and SceneDelegate- lifecycle methods
+ (void)handleUserActivity:(NSUserActivity *)userActivity window:(UIWindow *)window;

/// Common logic for handling user activities from AppDelegate-lifecycle methods.
+ (BOOL)handleAppDelegateURL:(NSURL *)URL app:(UIApplication *)app;

/// Posts a URL notification that will be handled by the emitter to JS; this method is used to invoke instance methods
/// of RCTLinkingManager from class methods via NSNotificationCenter.
/// @param URL The URL to be emitted.
+ (void)postNotificationWithURL:(NSURL *)URL;

@end

@implementation RCTLinkingManager

RCT_EXPORT_MODULE()

+ (void)postNotificationWithURL:(NSURL *)URL
{
NSDictionary<NSString *, id> *payload = @{@"url" : URL.absoluteString};
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification object:nil userInfo:payload];
}

- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}

#pragma mark - RCTEventEmitter methods

- (void)startObserving
{
[[NSNotificationCenter defaultCenter] addObserver:self
Expand All @@ -52,34 +66,70 @@ - (void)stopObserving
return @[ @"url" ];
}

#pragma mark - JS methods

+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)URL
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
{
postNotificationWithURL(URL, self);
return YES;
return [self handleAppDelegateURL:URL app:app];
}

// Corresponding api deprecated in iOS 9
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)URL
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
postNotificationWithURL(URL, self);
return YES;
return [self handleAppDelegateURL:URL app:application];
}

+ (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler
{
if (!RCTIsSceneDelegateApp()) {
[RCTLinkingManager handleUserActivity:userActivity window:RCTKeyWindow()];
return YES;
}

return NO;
}

#pragma mark - SceneDelegate methods

+ (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity
{
[RCTLinkingManager handleUserActivity:userActivity window:RCTKeyWindow()];
}

+ (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
{
if (URLContexts.count == 0) {
return;
}

NSURL *URL = URLContexts.allObjects.firstObject.URL;
[RCTLinkingManager postNotificationWithURL:URL];
}

#pragma mark - Common logic methods

+ (void)handleUserActivity:(NSUserActivity *)userActivity window:(UIWindow *)window
{
// This can be nullish when launching an App Clip.
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb] && userActivity.webpageURL != nil) {
NSDictionary *payload = @{@"url" : userActivity.webpageURL.absoluteString};
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification object:self userInfo:payload];
[RCTLinkingManager postNotificationWithURL:userActivity.webpageURL];
}
}

+ (BOOL)handleAppDelegateURL:(NSURL *)URL app:(UIApplication *)app
{
if (!RCTIsSceneDelegateApp()) {
[RCTLinkingManager postNotificationWithURL:URL];
return YES;
}
return YES;

return NO;
}

- (void)handleOpenURLNotification:(NSNotification *)notification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ describe('DeviceInfo', () => {

it('should give device info', () => {
expect(DeviceInfo.getConstants()).toHaveProperty('Dimensions');
expect(DeviceInfo.getInfo()).toHaveProperty('Dimensions');
});
});
3 changes: 3 additions & 0 deletions packages/react-native/React/Base/RCTUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ RCT_EXTERN UIApplication *__nullable RCTSharedApplication(void);
// or view controller
RCT_EXTERN UIWindow *__nullable RCTKeyWindow(void);

// Is this app a SceneDelegate app?
RCT_EXTERN BOOL RCTIsSceneDelegateApp(void);

// Returns the presented view controller, useful if you need
// e.g. to present a modal view controller or alert over it
RCT_EXTERN UIViewController *__nullable RCTPresentedViewController(void);
Expand Down
18 changes: 18 additions & 0 deletions packages/react-native/React/Base/RCTUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,24 @@ BOOL RCTRunningInAppExtension(void)
return nil;
}

BOOL RCTIsSceneDelegateApp(void)
{
if (@available(iOS 13.0, *)) {
NSDictionary *sceneManifest = [[NSBundle mainBundle] infoDictionary][@"UIApplicationSceneManifest"];

if (sceneManifest) {
NSDictionary *sceneConfigurations = sceneManifest[@"UIApplicationSceneConfigurations"];
if (sceneConfigurations && sceneConfigurations.count > 0) {
return YES;
}
}

return NO;
}

return NO;
}

UIStatusBarManager *__nullable RCTUIStatusBarManager(void)
{
return RCTKeyWindow().windowScene.statusBarManager;
Expand Down
15 changes: 8 additions & 7 deletions packages/react-native/React/CoreModules/RCTDevLoadingView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ - (void)showMessage:(NSString *)message
} else {
self->_window = [[UIWindow alloc] init];
}

self->_window.frame = self->_window.windowScene.coordinateSpace.bounds;

self->_window.windowLevel = UIWindowLevelStatusBar + 1;
self->_window.rootViewController = [UIViewController new];
[self->_window.rootViewController.view addSubview:self->_container];
Expand Down Expand Up @@ -221,13 +224,11 @@ - (void)showMessage:(NSString *)message
[NSLayoutConstraint activateConstraints:constraints];

[self->_window layoutIfNeeded];
self->_window.frame = CGRectMake(0, 0, mainWindow.frame.size.width, self->_container.frame.size.height);
});
}

RCT_EXPORT_METHOD(
showMessage : (NSString *)message withColor : (NSNumber *__nonnull)color withBackgroundColor : (NSNumber *__nonnull)
backgroundColor withDismissButton : (NSNumber *)dismissButton)
RCT_EXPORT_METHOD(showMessage : (NSString *)message withColor : (NSNumber *__nonnull)color withBackgroundColor : (
NSNumber *__nonnull)backgroundColor withDismissButton : (NSNumber *)dismissButton)
{
[self showMessage:message
color:[RCTConvert UIColor:color]
Expand All @@ -248,15 +249,15 @@ - (void)showMessage:(NSString *)message
const NSTimeInterval MIN_PRESENTED_TIME = 0.6;
NSTimeInterval presentedTime = [[NSDate date] timeIntervalSinceDate:self->_showDate];
NSTimeInterval delay = MAX(0, MIN_PRESENTED_TIME - presentedTime);
CGRect windowFrame = self->_window.frame;
CGFloat height = self->_container.bounds.size.height;
[UIView animateWithDuration:0.25
delay:delay
options:0
animations:^{
self->_window.frame = CGRectOffset(windowFrame, 0, -windowFrame.size.height);
self->_container.transform = CGAffineTransformMakeTranslation(0, -height);
}
completion:^(__unused BOOL finished) {
self->_window.frame = windowFrame;
self->_container.transform = CGAffineTransformIdentity;
self->_window.hidden = YES;
self->_window = nil;
self->_container = nil;
Expand Down
Loading
Loading