Skip to content

Commit 58055d2

Browse files
coadofacebook-github-bot
authored andcommitted
iOS: Add new configuration for RCTDevMenu (facebook#53505)
Summary: Following the [RFC](react-native-community/discussions-and-proposals#925), this PR adds new `RCTDevMenu` configuration and extends `RCTReactNativeFactory` API for passing it to the particular `RCTHost`. The `RCTDevMenuConfiguration` includes: - isDevMenuEnabled, - isShakeGestureEnabled, - areKeyboardShortcutsEnabled ## Changelog: [IOS][ADDED] - Add new configuration for `RCTDevMenu` <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests Test Plan: Tested with different configurations on `RCTDevMenuConfiguration`: <details> <summary>Click to view code</summary> ```objc - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self]; #if USE_OSS_CODEGEN self.dependencyProvider = [RCTAppDependencyProvider new]; #endif RCTDevMenuConfiguration *devMenuConfiguration = [[RCTDevMenuConfiguration alloc] initWithDevMenuEnabled:true shakeGestureEnabled:false keyboardShortcutsEnabled:false]; [self.reactNativeFactory setDevMenuConfiguration:devMenuConfiguration]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self.reactNativeFactory startReactNativeWithModuleName:@"RNTesterApp" inWindow:self.window initialProperties:[self prepareInitialProps] launchOptions:launchOptions]; [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; return YES; } ``` </details> Run helloworld: <img src="https://github.com/user-attachments/assets/16c35176-6fbe-498c-96fd-a0fe988a5e3c" alt="hello-world-screenshot" width="300"> Differential Revision: D81684275 Pulled By: coado
1 parent dc132a4 commit 58055d2

File tree

19 files changed

+372
-36
lines changed

19 files changed

+372
-36
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
@class RCTBridge;
2525
@protocol RCTComponentViewProtocol;
2626
@class RCTSurfacePresenterBridgeAdapter;
27+
@class RCTDevMenuConfiguration;
2728

2829
NS_ASSUME_NONNULL_BEGIN
2930

@@ -116,6 +117,8 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable };
116117

117118
@property (nonatomic, weak) id<RCTReactNativeFactoryDelegate> delegate;
118119

120+
@property (nonatomic, nullable) RCTDevMenuConfiguration *devMenuConfiguration;
121+
119122
@end
120123

121124
NS_ASSUME_NONNULL_END

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#import "RCTReactNativeFactory.h"
99
#import <React/RCTColorSpaceUtils.h>
10+
#import <React/RCTDevMenu.h>
1011
#import <React/RCTLog.h>
1112
#import <React/RCTRootView.h>
1213
#import <React/RCTSurfacePresenterBridgeAdapter.h>
@@ -82,7 +83,8 @@ - (void)startReactNativeWithModuleName:(NSString *)moduleName
8283
{
8384
UIView *rootView = [self.rootViewFactory viewWithModuleName:moduleName
8485
initialProperties:initialProperties
85-
launchOptions:launchOptions];
86+
launchOptions:launchOptions
87+
devMenuConfiguration:self.devMenuConfiguration];
8688
UIViewController *rootViewController = [_delegate createRootViewController];
8789
[_delegate setRootView:rootView toRootViewController:rootViewController];
8890
window.rootViewController = rootViewController;

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
@class RCTHost;
1919
@class RCTRootView;
2020
@class RCTSurfacePresenterBridgeAdapter;
21+
@class RCTDevMenuConfiguration;
2122

2223
NS_ASSUME_NONNULL_BEGIN
2324

@@ -202,6 +203,11 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc
202203
* @parameter: initialProperties - a set of initial properties.
203204
* @parameter: launchOptions - a dictionary with a set of options.
204205
*/
206+
- (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName
207+
initialProperties:(NSDictionary *__nullable)initialProperties
208+
launchOptions:(NSDictionary *__nullable)launchOptions
209+
devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration;
210+
205211
- (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName
206212
initialProperties:(NSDictionary *__nullable)initialProperties
207213
launchOptions:(NSDictionary *__nullable)launchOptions;
@@ -220,9 +226,11 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc
220226
*
221227
* @parameter: launchOptions - a dictionary with a set of options.
222228
*/
223-
- (void)initializeReactHostWithLaunchOptions:(NSDictionary *__nullable)launchOptions;
229+
- (void)initializeReactHostWithLaunchOptions:(NSDictionary *__nullable)launchOptions
230+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration;
224231

225-
- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions;
232+
- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions
233+
devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration;
226234

227235
@end
228236

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

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#import "RCTRootViewFactory.h"
99
#import <React/RCTCxxBridgeDelegate.h>
10+
#import <React/RCTDevMenu.h>
1011
#import <React/RCTLog.h>
1112
#import <React/RCTRootView.h>
1213
#import <React/RCTSurfacePresenterBridgeAdapter.h>
@@ -133,29 +134,47 @@ - (instancetype)initWithConfiguration:(RCTRootViewFactoryConfiguration *)configu
133134

134135
- (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties
135136
{
136-
return [self viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:nil];
137+
return [self viewWithModuleName:moduleName
138+
initialProperties:initialProperties
139+
launchOptions:nil
140+
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
137141
}
138142

139143
- (UIView *)viewWithModuleName:(NSString *)moduleName
140144
{
141-
return [self viewWithModuleName:moduleName initialProperties:nil launchOptions:nil];
145+
return [self viewWithModuleName:moduleName
146+
initialProperties:nil
147+
launchOptions:nil
148+
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
142149
}
143150

144151
- (void)initializeReactHostWithLaunchOptions:(NSDictionary *)launchOptions
152+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
145153
{
146154
// Enable TurboModule interop by default in Bridgeless mode
147155
RCTEnableTurboModuleInterop(YES);
148156
RCTEnableTurboModuleInteropBridgeProxy(YES);
149157

150-
[self createReactHostIfNeeded:launchOptions];
158+
[self createReactHostIfNeeded:launchOptions devMenuConfiguration:devMenuConfiguration];
151159
return;
152160
}
153161

162+
- (UIView *)viewWithModuleName:(NSString *)moduleName
163+
initialProperties:(NSDictionary *)initialProperties
164+
launchOptions:(NSDictionary *)launchOptions
165+
{
166+
return [self viewWithModuleName:moduleName
167+
initialProperties:initialProperties
168+
launchOptions:launchOptions
169+
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
170+
}
171+
154172
- (UIView *)viewWithModuleName:(NSString *)moduleName
155173
initialProperties:(NSDictionary *)initProps
156174
launchOptions:(NSDictionary *)launchOptions
175+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
157176
{
158-
[self initializeReactHostWithLaunchOptions:launchOptions];
177+
[self initializeReactHostWithLaunchOptions:launchOptions devMenuConfiguration:devMenuConfiguration];
159178

160179
RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName
161180
initialProperties:initProps ? initProps : @{}];
@@ -226,14 +245,16 @@ - (void)createBridgeAdapterIfNeeded
226245
#pragma mark - New Arch Utilities
227246

228247
- (void)createReactHostIfNeeded:(NSDictionary *)launchOptions
248+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
229249
{
230250
if (self.reactHost) {
231251
return;
232252
}
233-
self.reactHost = [self createReactHost:launchOptions];
253+
self.reactHost = [self createReactHost:launchOptions devMenuConfiguration:devMenuConfiguration];
234254
}
235255

236256
- (RCTHost *)createReactHost:(NSDictionary *)launchOptions
257+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
237258
{
238259
__weak __typeof(self) weakSelf = self;
239260
RCTHost *reactHost =
@@ -243,7 +264,8 @@ - (RCTHost *)createReactHost:(NSDictionary *)launchOptions
243264
jsEngineProvider:^std::shared_ptr<facebook::react::JSRuntimeFactory>() {
244265
return [weakSelf createJSRuntimeFactory];
245266
}
246-
launchOptions:launchOptions];
267+
launchOptions:launchOptions
268+
devMenuConfiguration:devMenuConfiguration];
247269
[reactHost setBundleURLProvider:^NSURL *() {
248270
return [weakSelf bundleURL];
249271
}];

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
const NSUInteger kRCTBundleURLProviderDefaultPort = RCT_METRO_PORT;
2020

21-
#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY
21+
#if RCT_DEV | RCT_PACKAGER_LOADING_FUNCTIONALITY
2222
static BOOL kRCTAllowPackagerAccess = YES;
2323
void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed)
2424
{
@@ -78,7 +78,7 @@ - (void)resetToDefaults
7878
(unsigned long)kRCTBundleURLProviderDefaultPort]];
7979
}
8080

81-
#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY
81+
#if RCT_DEV | RCT_PACKAGER_LOADING_FUNCTIONALITY
8282
+ (BOOL)isPackagerRunning:(NSString *)hostPort
8383
{
8484
return [RCTBundleURLProvider isPackagerRunning:hostPort scheme:nil];
@@ -155,14 +155,14 @@ - (NSString *)packagerServerHost
155155

156156
- (NSString *)packagerServerHostPort
157157
{
158-
#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY
158+
#if RCT_DEV | RCT_PACKAGER_LOADING_FUNCTIONALITY
159159
if (!kRCTAllowPackagerAccess) {
160160
RCTLogInfo(@"Packager server access is disabled in this environment");
161161
return nil;
162162
}
163163
#endif
164164
NSString *location = [self jsLocation];
165-
#if RCT_DEV_MENU
165+
#if RCT_DEV
166166
NSString *scheme = [self packagerScheme];
167167
if ([location length] && ![RCTBundleURLProvider isPackagerRunning:location scheme:scheme]) {
168168
location = nil;

packages/react-native/React/CoreModules/RCTDevMenu.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,26 @@
1212
#import <React/RCTBridgeProxy.h>
1313
#import <React/RCTDefines.h>
1414

15+
RCT_EXTERN NSString *const RCTShowDevMenuNotification;
16+
17+
@interface RCTDevMenuConfiguration : NSObject
18+
1519
#if RCT_DEV_MENU
1620

17-
RCT_EXTERN NSString *const RCTShowDevMenuNotification;
21+
@property (nonatomic, readonly) BOOL isDevMenuEnabled;
22+
@property (nonatomic, readonly) BOOL isShakeGestureEnabled;
23+
@property (nonatomic, readonly) BOOL areKeyboardShortcutsEnabled;
24+
25+
- (instancetype)initWithDevMenuEnabled:(BOOL)isDevMenuEnabled
26+
shakeGestureEnabled:(BOOL)isShakeGestureEnabled
27+
keyboardShortcutsEnabled:(BOOL)areKeyboardShortcutsEnabled;
28+
29+
+ (instancetype)defaultConfiguration;
1830

1931
#endif
2032

33+
@end
34+
2135
@class RCTDevMenuItem;
2236

2337
/**
@@ -45,6 +59,16 @@ RCT_EXTERN NSString *const RCTShowDevMenuNotification;
4559
*/
4660
@property (nonatomic, assign) BOOL hotkeysEnabled;
4761

62+
/**
63+
* Whether the developer menu is enabled.
64+
*/
65+
@property (nonatomic, assign) BOOL isDevMenuEnabled;
66+
67+
/**
68+
* Whether keyboard shortcuts are enabled.
69+
*/
70+
@property (nonatomic, assign) BOOL areKeyboardShortcutsEnabled;
71+
4872
/**
4973
* Presented items in development menu
5074
*/
@@ -76,6 +100,11 @@ RCT_EXTERN NSString *const RCTShowDevMenuNotification;
76100
*/
77101
- (void)addItem:(RCTDevMenuItem *)item;
78102

103+
/**
104+
* Disable the reload command (Cmd+R) in the simulator.
105+
*/
106+
- (void)disableReloadCommand;
107+
79108
@end
80109

81110
typedef NSString * (^RCTDevMenuItemTitleBlock)(void);

packages/react-native/React/CoreModules/RCTDevMenu.mm

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,27 @@ - (RCTDevMenuItem *)devMenuItem;
2929

3030
NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
3131

32+
@implementation RCTDevMenuConfiguration
33+
- (instancetype)initWithDevMenuEnabled:(BOOL)isDevMenuEnabled
34+
shakeGestureEnabled:(BOOL)isShakeGestureEnabled
35+
keyboardShortcutsEnabled:(BOOL)areKeyboardShortcutsEnabled
36+
{
37+
if (self = [super init]) {
38+
_isDevMenuEnabled = isDevMenuEnabled;
39+
_isShakeGestureEnabled = isShakeGestureEnabled;
40+
_areKeyboardShortcutsEnabled = areKeyboardShortcutsEnabled;
41+
}
42+
return self;
43+
}
44+
45+
+ (instancetype)defaultConfiguration
46+
{
47+
return [[self alloc] initWithDevMenuEnabled:RCT_DEV_MENU
48+
shakeGestureEnabled:RCT_DEV_MENU
49+
keyboardShortcutsEnabled:RCT_DEV_MENU];
50+
}
51+
@end
52+
3253
@implementation UIWindow (RCTDevMenu)
3354

3455
- (void)RCT_motionEnded:(__unused UIEventSubtype)motion withEvent:(UIEvent *)event
@@ -127,6 +148,8 @@ - (instancetype)init
127148
object:nil];
128149
_extraMenuItems = [NSMutableArray new];
129150

151+
_areKeyboardShortcutsEnabled = true;
152+
_isDevMenuEnabled = true;
130153
[self registerHotkeys];
131154
}
132155
return self;
@@ -162,7 +185,6 @@ - (void)unregisterHotkeys
162185

163186
[commands unregisterKeyCommandWithInput:@"d" modifierFlags:UIKeyModifierCommand];
164187
[commands unregisterKeyCommandWithInput:@"i" modifierFlags:UIKeyModifierCommand];
165-
[commands unregisterKeyCommandWithInput:@"n" modifierFlags:UIKeyModifierCommand];
166188
#endif
167189
}
168190

@@ -172,13 +194,30 @@ - (BOOL)isHotkeysRegistered
172194
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
173195

174196
return [commands isKeyCommandRegisteredForInput:@"d" modifierFlags:UIKeyModifierCommand] &&
175-
[commands isKeyCommandRegisteredForInput:@"i" modifierFlags:UIKeyModifierCommand] &&
176-
[commands isKeyCommandRegisteredForInput:@"n" modifierFlags:UIKeyModifierCommand];
197+
[commands isKeyCommandRegisteredForInput:@"i" modifierFlags:UIKeyModifierCommand];
198+
#else
199+
return NO;
200+
#endif
201+
}
202+
203+
- (BOOL)isReloadCommandRegistered
204+
{
205+
#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST
206+
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
207+
return [commands isKeyCommandRegisteredForInput:@"r" modifierFlags:UIKeyModifierCommand];
177208
#else
178209
return NO;
179210
#endif
180211
}
181212

213+
- (void)unregisterReloadCommand
214+
{
215+
#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST
216+
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
217+
[commands unregisterKeyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand];
218+
#endif
219+
}
220+
182221
- (dispatch_queue_t)methodQueue
183222
{
184223
return dispatch_get_main_queue();
@@ -240,6 +279,17 @@ - (void)addItem:(RCTDevMenuItem *)item
240279
[_extraMenuItems addObject:item];
241280
}
242281

282+
- (void)setAreKeyboardShortcutsEnabled:(BOOL)areKeyboardShortcutsEnabled
283+
{
284+
if (_areKeyboardShortcutsEnabled != areKeyboardShortcutsEnabled) {
285+
[self setHotkeysEnabled:areKeyboardShortcutsEnabled];
286+
287+
if (areKeyboardShortcutsEnabled == false) {
288+
[self disableReloadCommand];
289+
}
290+
}
291+
}
292+
243293
- (void)setDefaultJSBundle
244294
{
245295
[[RCTBundleURLProvider sharedSettings] resetToDefaults];
@@ -382,7 +432,7 @@ - (void)setDefaultJSBundle
382432

383433
RCT_EXPORT_METHOD(show)
384434
{
385-
if ((_actionSheet != nullptr) || RCTRunningInAppExtension()) {
435+
if ((_actionSheet != nullptr) || RCTRunningInAppExtension() || !_isDevMenuEnabled) {
386436
return;
387437
}
388438

@@ -437,7 +487,7 @@ - (RCTDevMenuAlertActionHandler)alertActionHandlerForDevItem:(RCTDevMenuItem *__
437487

438488
- (void)setShakeToShow:(BOOL)shakeToShow
439489
{
440-
((RCTDevSettings *)[_moduleRegistry moduleForName:"DevSettings"]).isShakeToShowDevMenuEnabled = shakeToShow;
490+
[[_moduleRegistry moduleForName:"DevSettings"] setIsShakeToShowDevMenuEnabled:shakeToShow];
441491
}
442492

443493
- (BOOL)shakeToShow
@@ -487,6 +537,13 @@ - (BOOL)hotkeysEnabled
487537
return [self isHotkeysRegistered];
488538
}
489539

540+
- (void)disableReloadCommand
541+
{
542+
if ([self isReloadCommandRegistered]) {
543+
[self unregisterReloadCommand];
544+
}
545+
}
546+
490547
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
491548
(const facebook::react::ObjCTurboModule::InitParams &)params
492549
{
@@ -515,6 +572,10 @@ - (void)addItem:(RCTDevMenu *)item
515572
{
516573
}
517574

575+
- (void)disableReloadCommand
576+
{
577+
}
578+
518579
- (BOOL)isActionSheetShown
519580
{
520581
return NO;

0 commit comments

Comments
 (0)