Skip to content

Commit 56c7a32

Browse files
coadofacebook-github-bot
authored andcommitted
iOS: Add new configuration for RCTDevMenu (#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> Differential Revision: D81684275 Pulled By: coado
1 parent 56e5dff commit 56c7a32

File tree

17 files changed

+296
-37
lines changed

17 files changed

+296
-37
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: 8 additions & 1 deletion
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,7 +226,8 @@ 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

225232
- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions;
226233

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

Lines changed: 25 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,44 @@ - (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:nil];
137141
}
138142

139143
- (UIView *)viewWithModuleName:(NSString *)moduleName
140144
{
141-
return [self viewWithModuleName:moduleName initialProperties:nil launchOptions:nil];
145+
return [self viewWithModuleName:moduleName initialProperties:nil launchOptions:nil devMenuConfiguration:nil];
142146
}
143147

144148
- (void)initializeReactHostWithLaunchOptions:(NSDictionary *)launchOptions
149+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
145150
{
146151
// Enable TurboModule interop by default in Bridgeless mode
147152
RCTEnableTurboModuleInterop(YES);
148153
RCTEnableTurboModuleInteropBridgeProxy(YES);
149154

150-
[self createReactHostIfNeeded:launchOptions];
155+
[self createReactHostIfNeeded:launchOptions devMenuConfiguration:devMenuConfiguration];
151156
return;
152157
}
153158

159+
- (UIView *)viewWithModuleName:(NSString *)moduleName
160+
initialProperties:(NSDictionary *)initialProperties
161+
launchOptions:(NSDictionary *)launchOptions
162+
{
163+
return [self viewWithModuleName:moduleName
164+
initialProperties:initialProperties
165+
launchOptions:launchOptions
166+
devMenuConfiguration:nil];
167+
}
168+
154169
- (UIView *)viewWithModuleName:(NSString *)moduleName
155170
initialProperties:(NSDictionary *)initProps
156171
launchOptions:(NSDictionary *)launchOptions
172+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
157173
{
158-
[self initializeReactHostWithLaunchOptions:launchOptions];
174+
[self initializeReactHostWithLaunchOptions:launchOptions devMenuConfiguration:devMenuConfiguration];
159175

160176
RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName
161177
initialProperties:initProps ? initProps : @{}];
@@ -226,14 +242,16 @@ - (void)createBridgeAdapterIfNeeded
226242
#pragma mark - New Arch Utilities
227243

228244
- (void)createReactHostIfNeeded:(NSDictionary *)launchOptions
245+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
229246
{
230247
if (self.reactHost) {
231248
return;
232249
}
233-
self.reactHost = [self createReactHost:launchOptions];
250+
self.reactHost = [self createReactHost:launchOptions devMenuConfiguration:devMenuConfiguration];
234251
}
235252

236253
- (RCTHost *)createReactHost:(NSDictionary *)launchOptions
254+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
237255
{
238256
__weak __typeof(self) weakSelf = self;
239257
RCTHost *reactHost =
@@ -243,7 +261,8 @@ - (RCTHost *)createReactHost:(NSDictionary *)launchOptions
243261
jsEngineProvider:^std::shared_ptr<facebook::react::JSRuntimeFactory>() {
244262
return [weakSelf createJSRuntimeFactory];
245263
}
246-
launchOptions:launchOptions];
264+
launchOptions:launchOptions
265+
devMenuConfiguration:devMenuConfiguration];
247266
[reactHost setBundleURLProvider:^NSURL *() {
248267
return [weakSelf bundleURL];
249268
}];

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: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,24 @@
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;
1828

1929
#endif
2030

31+
@end
32+
2133
@class RCTDevMenuItem;
2234

2335
/**
@@ -45,6 +57,16 @@ RCT_EXTERN NSString *const RCTShowDevMenuNotification;
4557
*/
4658
@property (nonatomic, assign) BOOL hotkeysEnabled;
4759

60+
/**
61+
* Whether the developer menu is enabled.
62+
*/
63+
@property (nonatomic, assign) BOOL isDevMenuEnabled;
64+
65+
/**
66+
* Whether keyboard shortcuts are enabled.
67+
*/
68+
@property (nonatomic, assign) BOOL areKeyboardShortcutsEnabled;
69+
4870
/**
4971
* Presented items in development menu
5072
*/
@@ -76,6 +98,11 @@ RCT_EXTERN NSString *const RCTShowDevMenuNotification;
7698
*/
7799
- (void)addItem:(RCTDevMenuItem *)item;
78100

101+
/**
102+
* Disable the reload command (Cmd+R) in the simulator.
103+
*/
104+
- (void)disableReloadCommand;
105+
79106
@end
80107

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

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

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ - (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+
@end
45+
3246
@implementation UIWindow (RCTDevMenu)
3347

3448
- (void)RCT_motionEnded:(__unused UIEventSubtype)motion withEvent:(UIEvent *)event
@@ -127,6 +141,8 @@ - (instancetype)init
127141
object:nil];
128142
_extraMenuItems = [NSMutableArray new];
129143

144+
_areKeyboardShortcutsEnabled = true;
145+
_isDevMenuEnabled = true;
130146
[self registerHotkeys];
131147
}
132148
return self;
@@ -162,7 +178,6 @@ - (void)unregisterHotkeys
162178

163179
[commands unregisterKeyCommandWithInput:@"d" modifierFlags:UIKeyModifierCommand];
164180
[commands unregisterKeyCommandWithInput:@"i" modifierFlags:UIKeyModifierCommand];
165-
[commands unregisterKeyCommandWithInput:@"n" modifierFlags:UIKeyModifierCommand];
166181
#endif
167182
}
168183

@@ -172,13 +187,30 @@ - (BOOL)isHotkeysRegistered
172187
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
173188

174189
return [commands isKeyCommandRegisteredForInput:@"d" modifierFlags:UIKeyModifierCommand] &&
175-
[commands isKeyCommandRegisteredForInput:@"i" modifierFlags:UIKeyModifierCommand] &&
176-
[commands isKeyCommandRegisteredForInput:@"n" modifierFlags:UIKeyModifierCommand];
190+
[commands isKeyCommandRegisteredForInput:@"i" modifierFlags:UIKeyModifierCommand];
191+
#else
192+
return NO;
193+
#endif
194+
}
195+
196+
- (BOOL)isReloadCommandRegistered
197+
{
198+
#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST
199+
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
200+
return [commands isKeyCommandRegisteredForInput:@"r" modifierFlags:UIKeyModifierCommand];
177201
#else
178202
return NO;
179203
#endif
180204
}
181205

206+
- (void)unregisterReloadCommand
207+
{
208+
#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST
209+
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
210+
[commands unregisterKeyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand];
211+
#endif
212+
}
213+
182214
- (dispatch_queue_t)methodQueue
183215
{
184216
return dispatch_get_main_queue();
@@ -240,6 +272,17 @@ - (void)addItem:(RCTDevMenuItem *)item
240272
[_extraMenuItems addObject:item];
241273
}
242274

275+
- (void)setAreKeyboardShortcutsEnabled:(BOOL)areKeyboardShortcutsEnabled
276+
{
277+
if (_areKeyboardShortcutsEnabled != areKeyboardShortcutsEnabled) {
278+
[self setHotkeysEnabled:areKeyboardShortcutsEnabled];
279+
280+
if (areKeyboardShortcutsEnabled == false) {
281+
[self disableReloadCommand];
282+
}
283+
}
284+
}
285+
243286
- (void)setDefaultJSBundle
244287
{
245288
[[RCTBundleURLProvider sharedSettings] resetToDefaults];
@@ -379,7 +422,7 @@ - (void)setDefaultJSBundle
379422

380423
RCT_EXPORT_METHOD(show)
381424
{
382-
if ((_actionSheet != nullptr) || RCTRunningInAppExtension()) {
425+
if ((_actionSheet != nullptr) || RCTRunningInAppExtension() || !_isDevMenuEnabled) {
383426
return;
384427
}
385428

@@ -427,7 +470,7 @@ - (RCTDevMenuAlertActionHandler)alertActionHandlerForDevItem:(RCTDevMenuItem *__
427470

428471
- (void)setShakeToShow:(BOOL)shakeToShow
429472
{
430-
((RCTDevSettings *)[_moduleRegistry moduleForName:"DevSettings"]).isShakeToShowDevMenuEnabled = shakeToShow;
473+
[[_moduleRegistry moduleForName:"DevSettings"] setIsShakeToShowDevMenuEnabled:shakeToShow];
431474
}
432475

433476
- (BOOL)shakeToShow
@@ -477,6 +520,13 @@ - (BOOL)hotkeysEnabled
477520
return [self isHotkeysRegistered];
478521
}
479522

523+
- (void)disableReloadCommand
524+
{
525+
if ([self isReloadCommandRegistered]) {
526+
[self unregisterReloadCommand];
527+
}
528+
}
529+
480530
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
481531
(const facebook::react::ObjCTurboModule::InitParams &)params
482532
{
@@ -505,6 +555,10 @@ - (void)addItem:(RCTDevMenu *)item
505555
{
506556
}
507557

558+
- (void)disableReloadCommand
559+
{
560+
}
561+
508562
- (BOOL)isActionSheetShown
509563
{
510564
return NO;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 <React/RCTBridgeModule.h>
9+
10+
@class RCTDevMenuConfiguration;
11+
12+
@interface RCTDevMenuConfigurationDecorator : NSObject
13+
14+
#if RCT_DEV_MENU
15+
16+
@property (nonatomic, strong, readonly) RCTDevMenuConfiguration *__nullable devMenuConfiguration;
17+
18+
- (instancetype)initWithDevMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration;
19+
- (void)decorate:(id<RCTBridgeModule>)devMenuModule;
20+
21+
#endif
22+
23+
@end

0 commit comments

Comments
 (0)