Skip to content

Commit d874dda

Browse files
committed
feat: add RCTSceneDelegate for UIScene-based iOS app bootstrap
Introduces an optional RCTSceneDelegate base class alongside the existing RCTAppDelegate path, with RCTGetActiveReactNativeFactory and RCTKeyWindow improvements.
1 parent dbad6af commit d874dda

12 files changed

Lines changed: 233 additions & 20 deletions

File tree

packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
NS_ASSUME_NONNULL_BEGIN
2020

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

6162
/// The window object, used to render the UViewControllers

packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ - (RCTColorSpace)defaultColorSpace
6666

6767
- (NSURL *_Nullable)bundleURL
6868
{
69-
[NSException raise:@"RCTAppDelegate::bundleURL not implemented"
69+
[NSException raise:@"RCTReactNativeFactoryDelegate::bundleURL not implemented"
7070
format:@"Subclasses must implement a valid getBundleURL method"];
7171
return nullptr;
7272
}

packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable };
5858

5959
@interface RCTReactNativeFactory : NSObject
6060

61+
/**
62+
* Bootstrap entrypoints:
63+
* - **AppDelegate path**: `startReactNativeWithModuleName:inWindow:launchOptions:` — call from
64+
* `application:didFinishLaunchingWithOptions:` or `RCTAppDelegate`.
65+
* - **SceneDelegate path**: `startReactNativeWithModuleName:inWindow:connectionOptions:` — call from
66+
* `scene:willConnectToSession:options:` or `RCTSceneDelegate`.
67+
*/
68+
6169
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate;
6270

6371
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate releaseLevel:(RCTReleaseLevel)releaseLevel;
@@ -99,8 +107,6 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable };
99107
@property (nonatomic, nullable) RCTBridge *bridge
100108
__attribute__((deprecated("The bridge is deprecated and will be removed when removing the legacy architecture.")));
101109

102-
@property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory;
103-
104110
#if !defined(RCT_REMOVE_LEGACY_ARCH)
105111
@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__((
106112
deprecated("The bridgeAdapter is deprecated and will be removed when removing the legacy architecture.")));

packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#import <React/RCTRootView.h>
1212
#import <React/RCTUtils.h>
1313
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
14-
#import "RCTAppDelegate.h"
1514
#import "RCTAppSetupUtils.h"
1615

1716
#if RN_DISABLE_OSS_PLUGIN_HEADER
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <UIKit/UIKit.h>
9+
#import "RCTDefaultReactNativeFactoryDelegate.h"
10+
#import "RCTReactNativeFactory.h"
11+
#import "RCTRootViewFactory.h"
12+
13+
@protocol RCTBridgeDelegate;
14+
@protocol RCTComponentViewProtocol;
15+
@class RCTRootView;
16+
@class RCTSurfacePresenterBridgeAdapter;
17+
@protocol RCTDependencyProvider;
18+
19+
NS_ASSUME_NONNULL_BEGIN
20+
21+
/**
22+
* Optional utility base class for SceneDelegate-based React Native apps using the UIScene lifecycle.
23+
*
24+
* For AppDelegate-only apps, use `RCTAppDelegate` or `RCTReactNativeFactory` directly.
25+
*
26+
* To use it, make your SceneDelegate a subclass of RCTSceneDelegate:
27+
*
28+
* ```objc
29+
* #import <React_RCTAppDelegate/RCTSceneDelegate.h>
30+
* @interface SceneDelegate : RCTSceneDelegate
31+
* @end
32+
* ```
33+
*
34+
* Requires `UIApplicationSceneManifest` in Info.plist with your SceneDelegate class configured.
35+
*
36+
* All methods implemented by RCTSceneDelegate can be overridden. Call `[super ...]` to use the default
37+
* implementation.
38+
*/
39+
@interface RCTSceneDelegate : RCTDefaultReactNativeFactoryDelegate <UIWindowSceneDelegate>
40+
41+
/// The window object used to render UIViewControllers for this scene.
42+
@property (nonatomic, strong, nullable) UIWindow *window;
43+
44+
#if !defined(RCT_REMOVE_LEGACY_ARCH)
45+
@property (nonatomic, nullable) RCTBridge *bridge
46+
__attribute__((deprecated("The bridge is deprecated and will be removed when removing the legacy architecture.")));
47+
@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__((
48+
deprecated("The bridge adapter is deprecated and will be removed when removing the legacy architecture.")));
49+
#endif
50+
51+
@property (nonatomic, strong, nullable) NSString *moduleName;
52+
@property (nonatomic, strong, nullable) NSDictionary *initialProps;
53+
@property (nonatomic, strong, nullable) RCTReactNativeFactory *reactNativeFactory;
54+
55+
/// If `automaticallyLoadReactNativeWindow` is set to `true`, the React Native window is loaded in
56+
/// `scene:willConnectToSession:options:`.
57+
@property (nonatomic, assign) BOOL automaticallyLoadReactNativeWindow;
58+
59+
- (RCTRootViewFactory *)rootViewFactory;
60+
61+
/// Loads the React Native root view into `self.window` using UIScene connection options.
62+
- (void)loadReactNativeWindow:(UISceneConnectionOptions *_Nullable)connectionOptions;
63+
64+
@end
65+
66+
NS_ASSUME_NONNULL_END
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTSceneDelegate.h"
9+
10+
#import <React/RCTLinkingManager.h>
11+
#import <React/RCTLog.h>
12+
#import <React/RCTRootView.h>
13+
#import <React/RCTUtils.h>
14+
15+
@implementation RCTSceneDelegate
16+
17+
- (instancetype)init
18+
{
19+
if (self = [super init]) {
20+
_automaticallyLoadReactNativeWindow = YES;
21+
}
22+
return self;
23+
}
24+
25+
- (void)scene:(UIScene *)scene
26+
willConnectToSession:(UISceneSession *)session
27+
options:(UISceneConnectionOptions *)connectionOptions
28+
{
29+
if (![scene isKindOfClass:[UIWindowScene class]]) {
30+
return;
31+
}
32+
33+
self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self];
34+
35+
UIWindowScene *windowScene = (UIWindowScene *)scene;
36+
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
37+
38+
if (self.automaticallyLoadReactNativeWindow) {
39+
[self loadReactNativeWindow:connectionOptions];
40+
}
41+
}
42+
43+
- (void)loadReactNativeWindow:(UISceneConnectionOptions *)connectionOptions
44+
{
45+
[self.reactNativeFactory startReactNativeWithModuleName:self.moduleName
46+
inWindow:self.window
47+
initialProperties:self.initialProps
48+
connectionOptions:connectionOptions];
49+
}
50+
51+
- (RCTRootViewFactory *)rootViewFactory
52+
{
53+
return self.reactNativeFactory.rootViewFactory;
54+
}
55+
56+
#pragma mark - Linking (SceneDelegate lifecycle)
57+
58+
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
59+
{
60+
[RCTLinkingManager scene:scene openURLContexts:URLContexts];
61+
}
62+
63+
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity
64+
{
65+
[RCTLinkingManager scene:scene continueUserActivity:userActivity];
66+
}
67+
68+
@end

packages/react-native/Libraries/AppDelegate/RCTUIConfiguratorProtocol.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
2020

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

packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.h

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,44 @@
1111

1212
@interface RCTLinkingManager : RCTEventEmitter
1313

14+
/**
15+
* Deep linking integration supports two iOS lifecycle paths:
16+
* - **AppDelegate methods** (below): use when the app does not declare `UIApplicationSceneManifest` in Info.plist.
17+
* - **SceneDelegate methods** (below): use when the app uses the UIScene lifecycle, or subclass `RCTSceneDelegate`
18+
* which forwards these automatically.
19+
*/
20+
1421
#pragma mark - AppDelegate methods
1522

16-
/// Lifecycle method informing of a URL being opened with the app, must be invoked from the AppDelegate.
17-
/// Must be invoked from the AppDelegate.
23+
/// Lifecycle method informing of a URL being opened with the app.
24+
/// Invoke from AppDelegate for non-scene apps (no `UIApplicationSceneManifest` in Info.plist).
1825
/// Note: this is an implementation using the iOS 9.0-26.0 API
1926
+ (BOOL)application:(nonnull UIApplication *)app
2027
openURL:(nonnull NSURL *)URL
2128
options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;
2229

23-
/// Lifecycle method handling a URL being opened with the app, must be invoked from the AppDelegate.
24-
/// Must be invoked from the AppDelegate.
30+
/// Lifecycle method handling a URL being opened with the app.
31+
/// Invoke from AppDelegate for non-scene apps.
2532
/// Note: this is an implementation using the iOS 4.2-9.0 API
2633
+ (BOOL)application:(nonnull UIApplication *)application
2734
openURL:(nonnull NSURL *)URL
2835
sourceApplication:(nullable NSString *)sourceApplication
2936
annotation:(nonnull id)annotation;
3037

3138
/// Lifecycle method handling user activity being performed.
32-
/// Must be invoked from the AppDelegate.
39+
/// Invoke from AppDelegate for non-scene apps.
3340
+ (BOOL)application:(nonnull UIApplication *)application
3441
continueUserActivity:(nonnull NSUserActivity *)userActivity
3542
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler;
3643

3744
#pragma mark - SceneDelegate methods
3845

39-
/// Successor to AppDelegate's application:continueUserActivity:restorationHandler:, which handles user activity being
40-
/// performed. Must be invoked from the SceneDelegate.
46+
/// Handles user activity for scene-based apps. Invoke from SceneDelegate, or use `RCTSceneDelegate` which forwards
47+
/// automatically.
4148
+ (void)scene:(nonnull UIScene *)scene continueUserActivity:(nonnull NSUserActivity *)userActivity;
4249

43-
/// Successor to AppDelegate's application:openURL:options:, which handles user activity being performed.
44-
/// Must be invoked from the SceneDelegate.
50+
/// Handles URLs opened while the app is running for scene-based apps. Invoke from SceneDelegate, or use
51+
/// `RCTSceneDelegate` which forwards automatically.
4552
+ (void)scene:(nonnull UIScene *)scene openURLContexts:(nonnull NSSet<UIOpenURLContext *> *)URLContexts;
4653

4754
@end

packages/react-native/React/Base/RCTJavaScriptLoader.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ static void attemptAsynchronousLoadOfBundleAtURL(
247247
[@"Could not connect to development server.\n\n"
248248
"Ensure the following:\n"
249249
"- Node server is running and available on the same network - run 'npm start' from react-native root\n"
250-
"- Node server URL is correctly set in AppDelegate\n"
250+
"- Node server URL is correctly set in your AppDelegate or SceneDelegate factory delegate\n"
251251
"- WiFi is enabled and connected to the same network as the Node Server\n\n"
252252
"URL: " stringByAppendingString:scriptURL.absoluteString],
253253
NSLocalizedFailureReasonErrorKey : error.localizedDescription,

packages/react-native/React/Base/RCTUtils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ RCT_EXTERN UIWindow *__nullable RCTKeyWindow(void);
9696
// Is this app a SceneDelegate app?
9797
RCT_EXTERN BOOL RCTIsSceneDelegateApp(void);
9898

99+
@class RCTReactNativeFactory;
100+
101+
/// Returns the active `RCTReactNativeFactory` from the foreground SceneDelegate or AppDelegate, if available.
102+
/// Useful for native code that needs access to `rootViewFactory` without holding a direct reference.
103+
RCT_EXTERN RCTReactNativeFactory *_Nullable RCTGetActiveReactNativeFactory(void);
104+
99105
// Returns the presented view controller, useful if you need
100106
// e.g. to present a modal view controller or alert over it
101107
RCT_EXTERN UIViewController *__nullable RCTPresentedViewController(void);

0 commit comments

Comments
 (0)