Skip to content

Commit e5c6976

Browse files
committed
update
1 parent 8b56883 commit e5c6976

3 files changed

Lines changed: 97 additions & 77 deletions

File tree

ios/RNNAppDelegate.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,20 @@
5656
@property(nonatomic) BOOL bridgelessEnabled;
5757
#endif
5858

59+
/**
60+
* Dispatch a deep link URL through React Native's Linking module so JS
61+
* subscribers (including RNN's built-in deep linking framework) receive it.
62+
*
63+
* Safe to call before the JS bridge is ready: URLs that arrive early
64+
* (e.g. cold-start notification taps) are queued natively and flushed
65+
* automatically once Fabric/React content first appears.
66+
*
67+
* Custom-scheme and universal-link openings dispatched by the OS are
68+
* forwarded through this method automatically; call it manually only
69+
* when your app receives a deep link from a source RNN can't intercept
70+
* (e.g. a custom `UNUserNotificationCenterDelegate`, a third-party push
71+
* SDK callback, etc.).
72+
*/
73+
- (void)dispatchDeepLinkURL:(NSURL *)url;
74+
5975
@end

ios/RNNAppDelegate.mm

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#import <React/RCTCxxBridgeDelegate.h>
99
#endif
1010
#import <React/RCTLegacyViewManagerInteropComponentView.h>
11+
#import <React/RCTLinkingManager.h>
12+
#import <React/RCTRootView.h>
1113
#import <React/RCTSurfacePresenter.h>
1214
#if __has_include(<React/RCTSurfacePresenterStub.h>)
1315
#import <React/RCTSurfacePresenterStub.h>
@@ -36,6 +38,13 @@
3638

3739
#import <React/RCTComponentViewFactory.h>
3840

41+
// Deep-link URLs that arrive (openURL, universal link, or external dispatch)
42+
// before the React runtime is ready are queued here and flushed when Fabric
43+
// posts `RCTContentDidAppearNotification` — by which point
44+
// `RCTLinkingManager` is instantiated and JS subscribers are listening.
45+
static NSMutableArray<NSURL *> *gRNNPendingDeepLinkURLs = nil;
46+
static BOOL gRNNReactRuntimeReady = NO;
47+
3948

4049
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
4150

@@ -92,9 +101,73 @@ - (BOOL)application:(UIApplication *)application
92101
[ReactNativeNavigation bootstrapWithHost:self.reactNativeFactory.rootViewFactory.reactHost];
93102
#endif
94103

104+
[self rnn_installDeepLinkObservers];
105+
95106
return YES;
96107
}
97108

109+
#pragma mark - Deep linking
110+
111+
// Forward OS-delivered custom-scheme URLs to React Native's Linking module.
112+
- (BOOL)application:(UIApplication *)application
113+
openURL:(NSURL *)url
114+
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
115+
[self dispatchDeepLinkURL:url];
116+
return YES;
117+
}
118+
119+
// Forward universal links (associated domains) to React Native's Linking
120+
// module by extracting the underlying https URL and routing it through the
121+
// same pre-bridge queue as everything else.
122+
- (BOOL)application:(UIApplication *)application
123+
continueUserActivity:(NSUserActivity *)userActivity
124+
restorationHandler:
125+
(void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler {
126+
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
127+
[self dispatchDeepLinkURL:userActivity.webpageURL];
128+
return YES;
129+
}
130+
return NO;
131+
}
132+
133+
- (void)dispatchDeepLinkURL:(NSURL *)url {
134+
if (url == nil) {
135+
return;
136+
}
137+
if (gRNNReactRuntimeReady) {
138+
[RCTLinkingManager application:[UIApplication sharedApplication]
139+
openURL:url
140+
options:@{}];
141+
return;
142+
}
143+
if (gRNNPendingDeepLinkURLs == nil) {
144+
gRNNPendingDeepLinkURLs = [NSMutableArray array];
145+
}
146+
[gRNNPendingDeepLinkURLs addObject:url];
147+
}
148+
149+
- (void)rnn_installDeepLinkObservers {
150+
// `RCTContentDidAppearNotification` is posted by Fabric's root view
151+
// once content has rendered. RNN forces bridgeless/new-arch, so the
152+
// legacy `RCTJavaScriptDidLoadNotification` never fires; we rely on
153+
// this Fabric signal exclusively.
154+
[[NSNotificationCenter defaultCenter] addObserver:self
155+
selector:@selector(rnn_handleReactRuntimeReady:)
156+
name:RCTContentDidAppearNotification
157+
object:nil];
158+
}
159+
160+
- (void)rnn_handleReactRuntimeReady:(NSNotification *)notification {
161+
gRNNReactRuntimeReady = YES;
162+
NSArray<NSURL *> *pending = [gRNNPendingDeepLinkURLs copy];
163+
[gRNNPendingDeepLinkURLs removeAllObjects];
164+
for (NSURL *url in pending) {
165+
[RCTLinkingManager application:[UIApplication sharedApplication]
166+
openURL:url
167+
options:@{}];
168+
}
169+
}
170+
98171

99172
#if !RNN_RN_VERSION_79_OR_NEWER
100173
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {

playground/ios/playground/AppDelegate.mm

Lines changed: 8 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
#import "AppDelegate.h"
22
#import "RNNCustomViewController.h"
33
#import <ReactNativeNavigation/ReactNativeNavigation.h>
4-
#import <React/RCTBridge.h>
5-
#import <React/RCTLinkingManager.h>
6-
#import <React/RCTRootView.h>
74
#import <UserNotifications/UserNotifications.h>
85

9-
// URLs that arrive (notification tap, openURL) before the JS bridge is
10-
// ready are queued here and flushed when the bridge finishes loading.
11-
// Without this, cold-start notifications would post `RCTOpenURLNotification`
12-
// into the void because RCTLinkingManager hasn't subscribed yet.
13-
static NSMutableArray<NSURL *> *gPendingDeepLinkURLs = nil;
14-
static BOOL gJavaScriptDidLoad = NO;
15-
166
#if !RNN_RN_VERSION_79_OR_NEWER
177
@interface AppDelegate () <RCTBridgeDelegate, UNUserNotificationCenterDelegate>
188
@end
@@ -63,74 +53,16 @@ - (BOOL)application:(UIApplication *)application
6353
return [[RNNCustomViewController alloc] initWithProps:props];
6454
}];
6555

66-
// Receive notification taps (Detox sendUserNotification & real push taps)
56+
// Demo: route notification taps through RNN's deep-linking pipeline by
57+
// installing this AppDelegate as the UNUserNotificationCenter delegate.
58+
// (Apps that already own this delegate via Firebase/OneSignal/etc. can
59+
// instead call `[self dispatchDeepLinkURL:url]` from their existing
60+
// handler — same effect.)
6761
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
6862

69-
// Flush deep links that arrived before the JS bridge was up.
70-
// In legacy mode `RCTJavaScriptDidLoadNotification` fires after the
71-
// bridge loads JS. In bridgeless/new-arch that notification does NOT
72-
// fire, so we also listen for `RCTContentDidAppearNotification` which
73-
// is posted by Fabric's root view once content has rendered — by which
74-
// point RCTLinkingManager is already instantiated and listening.
75-
[[NSNotificationCenter defaultCenter] addObserver:self
76-
selector:@selector(handleJavaScriptDidLoad:)
77-
name:RCTJavaScriptDidLoadNotification
78-
object:nil];
79-
[[NSNotificationCenter defaultCenter] addObserver:self
80-
selector:@selector(handleJavaScriptDidLoad:)
81-
name:RCTContentDidAppearNotification
82-
object:nil];
83-
8463
return YES;
8564
}
8665

87-
#pragma mark - Deep linking
88-
89-
// Forward foreground URL openings (custom schemes & universal links)
90-
// to React Native's Linking module so JS can handle them.
91-
- (BOOL)application:(UIApplication *)application
92-
openURL:(NSURL *)url
93-
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
94-
[self dispatchDeepLinkURL:url];
95-
return YES;
96-
}
97-
98-
// Dispatch a deep link URL. If RCTLinkingManager hasn't subscribed yet
99-
// (cold-start, JS bridge still loading), queue it for replay after
100-
// RCTJavaScriptDidLoadNotification fires.
101-
- (void)dispatchDeepLinkURL:(NSURL *)url {
102-
if (url == nil) { return; }
103-
if (gJavaScriptDidLoad) {
104-
[RCTLinkingManager application:[UIApplication sharedApplication]
105-
openURL:url
106-
options:@{}];
107-
return;
108-
}
109-
if (gPendingDeepLinkURLs == nil) {
110-
gPendingDeepLinkURLs = [NSMutableArray array];
111-
}
112-
[gPendingDeepLinkURLs addObject:url];
113-
}
114-
115-
- (void)handleJavaScriptDidLoad:(NSNotification *)notification {
116-
gJavaScriptDidLoad = YES;
117-
NSArray<NSURL *> *pending = [gPendingDeepLinkURLs copy];
118-
[gPendingDeepLinkURLs removeAllObjects];
119-
for (NSURL *url in pending) {
120-
[RCTLinkingManager application:[UIApplication sharedApplication]
121-
openURL:url
122-
options:@{}];
123-
}
124-
}
125-
126-
- (BOOL)application:(UIApplication *)application
127-
continueUserActivity:(NSUserActivity *)userActivity
128-
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *))restorationHandler {
129-
return [RCTLinkingManager application:application
130-
continueUserActivity:userActivity
131-
restorationHandler:restorationHandler];
132-
}
133-
13466
#pragma mark - UNUserNotificationCenterDelegate
13567

13668
// Surface notifications while the app is in the foreground so the user
@@ -150,9 +82,9 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
15082
UNNotificationPresentationOptionSound);
15183
}
15284

153-
// Notification tap → if payload carries `url`, route it through the
154-
// existing Linking pipeline so deep linking reacts the same way regardless
155-
// of whether the URL came from the OS or a notification.
85+
// Notification tap → if payload carries `url`, route it through RNN's
86+
// deep-linking pipeline (inherited `dispatchDeepLinkURL:` queues until
87+
// the React runtime is ready, then forwards to RCTLinkingManager).
15688
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
15789
didReceiveNotificationResponse:(UNNotificationResponse *)response
15890
withCompletionHandler:(void (^)(void))completionHandler {
@@ -183,4 +115,3 @@ - (NSURL *)bundleURL
183115
#endif
184116

185117
@end
186-

0 commit comments

Comments
 (0)