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
17 changes: 7 additions & 10 deletions packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
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, implement your own `SceneDelegate` with `RCTReactNativeFactory` (see integration
* docs). For AppDelegate-only apps, use `RCTReactNativeFactory` directly.
*
* Scene-based apps must keep `UIApplicationSupportsMultipleScenes` set to `false` in Info.plist. Define
* `RN_ALLOW_MULTIPLE_SCENES` on the app target to downgrade the unsupported-configuration crash to a warning.
*
* 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,19 +59,12 @@ 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 implement your own SceneDelegate with RCTReactNativeFactory; otherwise use RCTReactNativeFactory.")))
@interface RCTAppDelegate : RCTDefaultReactNativeFactoryDelegate<UIApplicationDelegate>

/// The window object, used to render the UViewControllers
@property (nonatomic, strong, nonnull) 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) RCTReactNativeFactory *reactNativeFactory;
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,15 @@ 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:` in your app-owned `SceneDelegate` (subclass
* `RCTDefaultReactNativeFactoryDelegate` and conform to `UIWindowSceneDelegate`).
*/

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

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

#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.")));
/**
* SceneDelegate entrypoint to start a React Native instance with the specified module name, window, and connection
* options for linking and user activity information. Only the first item in `URLContexts` and `userActivities` is used.
* @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;

/**
* SceneDelegate entrypoint to start a React Native instance with the specified module name, window, initial properties,
* and connection options. Only the first item in `URLContexts` and `userActivities` is used.
* @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;

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

@property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#import <React/RCTBundleManager.h>
#import <React/RCTColorSpaceUtils.h>
#import <React/RCTDevMenu.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <React/RCTUtils.h>
Expand Down Expand Up @@ -40,8 +39,36 @@ @interface RCTReactNativeFactory () <
RCTHostDelegate,
RCTJSRuntimeConfiguratorProtocol,
RCTTurboModuleManagerDelegate>

@end

static NSDictionary *RCTConvertConnectionOptionsToLaunchOptions(UISceneConnectionOptions *connectionOptions)
{
NSMutableDictionary *launchOptions = [NSMutableDictionary dictionary];

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

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

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;
}

@implementation RCTReactNativeFactory

@synthesize bundleConfiguration = _bundleConfiguration;
Expand Down Expand Up @@ -95,6 +122,29 @@ - (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:RCTConvertConnectionOptionsToLaunchOptions(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:RCTConvertConnectionOptionsToLaunchOptions(connectionOptions)];
}

#pragma mark - RCTUIConfiguratorProtocol

- (RCTColorSpace)defaultColorSpace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,6 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc
*/
@interface RCTRootViewFactory : NSObject

#if !defined(RCT_REMOVE_LEGACY_ARCH)
@property (nonatomic, strong, nullable) RCTBridge *bridge;
@property (nonatomic, strong, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter;
#endif

@property (nonatomic, strong, nullable) RCTHost *reactHost;

- (instancetype)initWithConfiguration:(RCTRootViewFactoryConfiguration *)configuration
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
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, or
* `RCTAppDelegate` subclass). Example:
* ```
* - (void)customizeRootView:(RCTRootView *)rootView
* {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Pod::Spec.new do |s|
s.dependency "RCTTypeSafety"
s.dependency "React-RCTNetwork"
s.dependency "React-RCTImage"
s.dependency "React-RCTLinking"
s.dependency "React-CoreModules"
s.dependency "React-RCTFBReactNativeSpec"
s.dependency "React-defaultsnativemodule"
Expand Down
25 changes: 25 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,42 @@

@interface RCTLinkingManager : RCTEventEmitter

/**
* Deep linking integration supports two iOS lifecycle paths:
* - **AppDelegate methods** (below): use when the app does not declare `UIApplicationSceneManifest` in Info.plist.
* - **SceneDelegate methods** (below): use when the app uses the UIScene lifecycle. Forward these from your
* app-owned `SceneDelegate`.
*/

#pragma mark - AppDelegate methods

/// Lifecycle method informing of a URL being opened with the app.
/// Invoke from AppDelegate for non-scene apps (no `UIApplicationSceneManifest` in Info.plist).
/// 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.
/// Invoke from AppDelegate for non-scene apps.
/// 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.
/// Invoke from AppDelegate for non-scene apps.
+ (BOOL)application:(nonnull UIApplication *)application
continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler;

#pragma mark - SceneDelegate methods

/// Handles user activity for scene-based apps. Invoke from your SceneDelegate.
+ (void)scene:(nonnull UIScene *)scene continueUserActivity:(nonnull NSUserActivity *)userActivity;

/// Handles URLs opened while the app is running for scene-based apps. Invoke from your SceneDelegate.
+ (void)scene:(nonnull UIScene *)scene openURLContexts:(nonnull NSSet<UIOpenURLContext *> *)URLContexts;

@end
76 changes: 63 additions & 13 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,33 +66,69 @@ - (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()) {
return NO;
}

[RCTLinkingManager handleUserActivity:userActivity window:RCTKeyWindow()];
return YES;
}

#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()) {
return NO;
}

[RCTLinkingManager postNotificationWithURL:URL];
return YES;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/React/Base/RCTJavaScriptLoader.mm
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ static void attemptAsynchronousLoadOfBundleAtURL(
[@"Could not connect to development server.\n\n"
"Ensure the following:\n"
"- Node server is running and available on the same network - run 'npm start' from react-native root\n"
"- Node server URL is correctly set in AppDelegate\n"
"- Node server URL is correctly set in your AppDelegate or SceneDelegate factory delegate\n"
"- WiFi is enabled and connected to the same network as the Node Server\n\n"
"URL: " stringByAppendingString:scriptURL.absoluteString],
NSLocalizedFailureReasonErrorKey : error.localizedDescription,
Expand Down
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 @@ -93,6 +93,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
Loading
Loading