mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
8131b7bb7b
Summary: This is my proposal for fixing `use_frameworks!` compatibility without breaking all `<React/*>` imports I outlined in https://github.com/facebook/react-native/pull/25393#issuecomment-508457700. If accepted, it will fix https://github.com/facebook/react-native/issues/25349. It builds on the changes I made in https://github.com/facebook/react-native/pull/25496 by ensuring each podspec has a unique value for `header_dir` so that framework imports do not conflict. Every podspec which should be included in the `<React/*>` namespace now includes it's headers from `React-Core.podspec`. The following pods can still be imported with `<React/*>` and so should not have breaking changes: `React-ART`,`React-DevSupport`, `React-CoreModules`, `React-RCTActionSheet`, `React-RCTAnimation`, `React-RCTBlob`, `React-RCTImage`, `React-RCTLinking`, `React-RCTNetwork`, `React-RCTPushNotification`, `React-RCTSettings`, `React-RCTText`, `React-RCTSettings`, `React-RCTVibration`, `React-RCTWebSocket` . There are still a few breaking changes which I hope will be acceptable: - `React-Core.podspec` has been moved to the root of the project. Any `Podfile` that references it will need to update the path. - ~~`React-turbomodule-core`'s headers now live under `<turbomodule/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511091823. - ~~`React-turbomodulesamples`'s headers now live under `<turbomodulesamples/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511091823. - ~~`React-TypeSaferty`'s headers now live under `<TypeSafety/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511040967. - ~~`React-jscallinvoker`'s headers now live under `<jscallinvoker/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511091823. - Each podspec now uses `s.static_framework = true`. This means that a minimum of CocoaPods 1.5 ([released in April 2018](http://blog.cocoapods.org/CocoaPods-1.5.0/)) is now required. This is needed so that the ` __has_include` conditions can still work when frameworks are enabled. Still to do: - ~~Including `React-turbomodule-core` with `use_frameworks!` enabled causes the C++ import failures we saw in https://github.com/facebook/react-native/issues/25349. I'm sure it will be possible to fix this but I need to dig deeper (perhaps a custom modulemap would be needed).~~ Addressed by https://github.com/facebook/react-native/pull/25619/commits/33573511f02f3502a28bad48e085e9a4b8608302. - I haven't got Fabric working yet. I wonder if it would be acceptable to move Fabric out of the `<React/*>` namespace since it is new? � ## Changelog [iOS] [Fixed] - Fixed compatibility with CocoaPods frameworks. Pull Request resolved: https://github.com/facebook/react-native/pull/25619 Test Plan: ### FB ``` buck build catalyst ``` ### Sample Project Everything should work exactly as before, where `use_frameworks!` is not in `Podfile`s. I have a branch on my [sample project](https://github.com/jtreanor/react-native-cocoapods-frameworks) here which has `use_frameworks!` in its `Podfile` to demonstrate this is fixed. You can see that it works with these steps: 1. `git clone git@github.com:jtreanor/react-native-cocoapods-frameworks.git` 2. `git checkout fix-frameworks-subspecs` 3. `cd ios && pod install` 4. `cd .. && react-native run-ios` The sample app will build and run successfully. To see that it still works without frameworks, remove `use_frameworks!` from the `Podfile` and do steps 3 and 4 again. ### RNTesterPods `RNTesterPodsPods` can now work with or without `use_frameworks!`. 1. Go to the `RNTester` directory and run `pod install`. 2. Run the tests in `RNTesterPods.xcworkspace` to see that everything still works fine. 3. Uncomment the `use_frameworks!` line at the top of `RNTester/Podfile` and run `pod install` again. 4. Run the tests again and see that it still works with frameworks enabled. Reviewed By: PeteTheHeat Differential Revision: D16465247 Pulled By: PeteTheHeat fbshipit-source-id: cad837e9cced06d30cc5b372af1c65c7780b9e7a
488 lines
16 KiB
Objective-C
488 lines
16 KiB
Objective-C
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import <React/RCTDevMenu.h>
|
|
|
|
#import <React/RCTBridge+Private.h>
|
|
#import <React/RCTDevSettings.h>
|
|
#import <React/RCTKeyCommands.h>
|
|
#import <React/RCTLog.h>
|
|
#import <React/RCTUtils.h>
|
|
#import <React/RCTDefines.h>
|
|
#import <React/RCTBundleURLProvider.h>
|
|
|
|
#if RCT_DEV
|
|
|
|
#if RCT_ENABLE_INSPECTOR
|
|
#import <React/RCTInspectorDevServerHelper.h>
|
|
#endif
|
|
|
|
NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
|
|
|
|
@implementation UIWindow (RCTDevMenu)
|
|
|
|
- (void)RCT_motionEnded:(__unused UIEventSubtype)motion withEvent:(UIEvent *)event
|
|
{
|
|
if (event.subtype == UIEventSubtypeMotionShake) {
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTDevMenuItem
|
|
{
|
|
RCTDevMenuItemTitleBlock _titleBlock;
|
|
dispatch_block_t _handler;
|
|
}
|
|
|
|
- (instancetype)initWithTitleBlock:(RCTDevMenuItemTitleBlock)titleBlock
|
|
handler:(dispatch_block_t)handler
|
|
{
|
|
if ((self = [super init])) {
|
|
_titleBlock = [titleBlock copy];
|
|
_handler = [handler copy];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|
|
|
+ (instancetype)buttonItemWithTitleBlock:(NSString *(^)(void))titleBlock handler:(dispatch_block_t)handler
|
|
{
|
|
return [[self alloc] initWithTitleBlock:titleBlock handler:handler];
|
|
}
|
|
|
|
+ (instancetype)buttonItemWithTitle:(NSString *)title
|
|
handler:(dispatch_block_t)handler
|
|
{
|
|
return [[self alloc] initWithTitleBlock:^NSString *{ return title; } handler:handler];
|
|
}
|
|
|
|
- (void)callHandler
|
|
{
|
|
if (_handler) {
|
|
_handler();
|
|
}
|
|
}
|
|
|
|
- (NSString *)title
|
|
{
|
|
if (_titleBlock) {
|
|
return _titleBlock();
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
@end
|
|
|
|
typedef void(^RCTDevMenuAlertActionHandler)(UIAlertAction *action);
|
|
|
|
@interface RCTDevMenu () <RCTBridgeModule, RCTInvalidating>
|
|
|
|
@end
|
|
|
|
@implementation RCTDevMenu
|
|
{
|
|
UIAlertController *_actionSheet;
|
|
NSMutableArray<RCTDevMenuItem *> *_extraMenuItems;
|
|
}
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
+ (void)initialize
|
|
{
|
|
// We're swizzling here because it's poor form to override methods in a category,
|
|
// however UIWindow doesn't actually implement motionEnded:withEvent:, so there's
|
|
// no need to call the original implementation.
|
|
RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:));
|
|
}
|
|
|
|
+ (BOOL)requiresMainQueueSetup
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(showOnShake)
|
|
name:RCTShowDevMenuNotification
|
|
object:nil];
|
|
_extraMenuItems = [NSMutableArray new];
|
|
|
|
#if TARGET_OS_SIMULATOR
|
|
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
|
|
__weak __typeof(self) weakSelf = self;
|
|
|
|
// Toggle debug menu
|
|
[commands registerKeyCommandWithInput:@"d"
|
|
modifierFlags:UIKeyModifierCommand
|
|
action:^(__unused UIKeyCommand *command) {
|
|
[weakSelf toggle];
|
|
}];
|
|
|
|
// Toggle element inspector
|
|
[commands registerKeyCommandWithInput:@"i"
|
|
modifierFlags:UIKeyModifierCommand
|
|
action:^(__unused UIKeyCommand *command) {
|
|
[weakSelf.bridge.devSettings toggleElementInspector];
|
|
}];
|
|
|
|
// Reload in normal mode
|
|
[commands registerKeyCommandWithInput:@"n"
|
|
modifierFlags:UIKeyModifierCommand
|
|
action:^(__unused UIKeyCommand *command) {
|
|
[weakSelf.bridge.devSettings setIsDebuggingRemotely:NO];
|
|
}];
|
|
#endif
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (dispatch_queue_t)methodQueue
|
|
{
|
|
return dispatch_get_main_queue();
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
_presentedItems = nil;
|
|
[_actionSheet dismissViewControllerAnimated:YES completion:^(void){}];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
- (void)showOnShake
|
|
{
|
|
if ([_bridge.devSettings isShakeToShowDevMenuEnabled]) {
|
|
[self show];
|
|
}
|
|
}
|
|
|
|
- (void)toggle
|
|
{
|
|
if (_actionSheet) {
|
|
[_actionSheet dismissViewControllerAnimated:YES completion:^(void){}];
|
|
_actionSheet = nil;
|
|
} else {
|
|
[self show];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isActionSheetShown
|
|
{
|
|
return _actionSheet != nil;
|
|
}
|
|
|
|
- (void)addItem:(NSString *)title handler:(void(^)(void))handler
|
|
{
|
|
[self addItem:[RCTDevMenuItem buttonItemWithTitle:title handler:handler]];
|
|
}
|
|
|
|
- (void)addItem:(RCTDevMenuItem *)item
|
|
{
|
|
[_extraMenuItems addObject:item];
|
|
}
|
|
|
|
- (void)setDefaultJSBundle {
|
|
[[RCTBundleURLProvider sharedSettings] resetToDefaults];
|
|
self->_bridge.bundleURL = [[RCTBundleURLProvider sharedSettings] jsBundleURLForFallbackResource:nil fallbackExtension:nil];
|
|
[self->_bridge reload];
|
|
}
|
|
|
|
- (NSArray<RCTDevMenuItem *> *)_menuItemsToPresent
|
|
{
|
|
NSMutableArray<RCTDevMenuItem *> *items = [NSMutableArray new];
|
|
|
|
// Add built-in items
|
|
__weak RCTBridge *bridge = _bridge;
|
|
__weak RCTDevSettings *devSettings = _bridge.devSettings;
|
|
__weak RCTDevMenu *weakSelf = self;
|
|
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Reload" handler:^{
|
|
[bridge reload];
|
|
}]];
|
|
|
|
if (!devSettings.isProfilingEnabled) {
|
|
if (!devSettings.isRemoteDebuggingAvailable) {
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Debugger Unavailable" handler:^{
|
|
NSString *message = RCTTurboModuleEnabled() ?
|
|
@"Debugging is not currently supported when TurboModule is enabled." :
|
|
@"Include the RCTWebSocket library to enable JavaScript debugging.";
|
|
UIAlertController *alertController = [UIAlertController
|
|
alertControllerWithTitle:@"Debugger Unavailable"
|
|
message:message
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
__weak typeof(alertController) weakAlertController = alertController;
|
|
[alertController addAction:
|
|
[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action){
|
|
[weakAlertController dismissViewControllerAnimated:YES completion:nil];
|
|
}]];
|
|
[RCTPresentedViewController() presentViewController:alertController animated:YES completion:NULL];
|
|
}]];
|
|
} else {
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{
|
|
if (devSettings.isNuclideDebuggingAvailable) {
|
|
return devSettings.isDebuggingRemotely ? @"Stop Chrome Debugger" : @"Debug with Chrome";
|
|
} else {
|
|
return devSettings.isDebuggingRemotely ? @"Stop Debugging" : @"Debug";
|
|
}
|
|
} handler:^{
|
|
devSettings.isDebuggingRemotely = !devSettings.isDebuggingRemotely;
|
|
}]];
|
|
}
|
|
|
|
if (devSettings.isNuclideDebuggingAvailable && !devSettings.isDebuggingRemotely) {
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Debug with Nuclide" handler:^{
|
|
#if RCT_ENABLE_INSPECTOR && !TARGET_OS_UIKITFORMAC
|
|
[RCTInspectorDevServerHelper attachDebugger:@"ReactNative" withBundleURL:bridge.bundleURL withView: RCTPresentedViewController()];
|
|
#endif
|
|
}]];
|
|
}
|
|
}
|
|
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{
|
|
return devSettings.isElementInspectorShown ? @"Hide Inspector" : @"Show Inspector";
|
|
} handler:^{
|
|
[devSettings toggleElementInspector];
|
|
}]];
|
|
|
|
if (devSettings.isHotLoadingAvailable) {
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{
|
|
// Previously known as "Hot Reloading". We won't use this term anymore.
|
|
return devSettings.isHotLoadingEnabled ? @"Disable Fast Refresh" : @"Enable Fast Refresh";
|
|
} handler:^{
|
|
devSettings.isHotLoadingEnabled = !devSettings.isHotLoadingEnabled;
|
|
}]];
|
|
}
|
|
|
|
if (devSettings.isLiveReloadAvailable) {
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{
|
|
return devSettings.isDebuggingRemotely
|
|
? @"Systrace Unavailable"
|
|
: devSettings.isProfilingEnabled
|
|
? @"Stop Systrace"
|
|
: @"Start Systrace";
|
|
} handler:^{
|
|
if (devSettings.isDebuggingRemotely) {
|
|
UIAlertController *alertController = [UIAlertController
|
|
alertControllerWithTitle:@"Systrace Unavailable"
|
|
message:@"Stop debugging to enable Systrace."
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
__weak typeof(alertController) weakAlertController = alertController;
|
|
[alertController addAction:
|
|
[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action){
|
|
[weakAlertController dismissViewControllerAnimated:YES completion:nil];
|
|
}]];
|
|
[RCTPresentedViewController() presentViewController:alertController animated:YES completion:NULL];
|
|
} else {
|
|
devSettings.isProfilingEnabled = !devSettings.isProfilingEnabled;
|
|
}
|
|
}]];
|
|
// "Live reload" which refreshes on every edit was removed in favor of "Fast Refresh".
|
|
// While native code for "Live reload" is still there, please don't add the option back.
|
|
// See D15958697 for more context.
|
|
}
|
|
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{
|
|
return @"Configure Bundler";
|
|
} handler:^{
|
|
UIAlertController * alertController = [UIAlertController alertControllerWithTitle: @"Configure Bundler"
|
|
message: @"Provide a custom bundler address, port, and entrypoint."
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
|
textField.placeholder = @"0.0.0.0";
|
|
}];
|
|
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
|
textField.placeholder = @"8081";
|
|
}];
|
|
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
|
textField.placeholder = @"index";
|
|
}];
|
|
[alertController addAction:[UIAlertAction actionWithTitle:@"Apply Changes" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
|
|
NSArray * textfields = alertController.textFields;
|
|
UITextField * ipTextField = textfields[0];
|
|
UITextField * portTextField = textfields[1];
|
|
UITextField * bundleRootTextField = textfields[2];
|
|
NSString * bundleRoot = bundleRootTextField.text;
|
|
if(ipTextField.text.length == 0 && portTextField.text.length == 0) {
|
|
[weakSelf setDefaultJSBundle];
|
|
return;
|
|
}
|
|
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
|
formatter.numberStyle = NSNumberFormatterDecimalStyle;
|
|
NSNumber *portNumber = [formatter numberFromString:portTextField.text];
|
|
if (portNumber == nil) {
|
|
portNumber = [NSNumber numberWithInt: RCT_METRO_PORT];
|
|
}
|
|
[RCTBundleURLProvider sharedSettings].jsLocation = [NSString stringWithFormat:@"%@:%d",
|
|
ipTextField.text, portNumber.intValue];
|
|
__strong RCTBridge *strongBridge = bridge;
|
|
if (strongBridge) {
|
|
NSURL *bundleURL = bundleRoot.length
|
|
? [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:bundleRoot fallbackResource:nil]
|
|
: [strongBridge.delegate sourceURLForBridge:strongBridge];
|
|
strongBridge.bundleURL = bundleURL;
|
|
[strongBridge reload];
|
|
}
|
|
}]];
|
|
[alertController addAction:[UIAlertAction actionWithTitle:@"Reset to Default" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
|
|
[weakSelf setDefaultJSBundle];
|
|
}]];
|
|
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction *action) {
|
|
return;
|
|
}]];
|
|
[RCTPresentedViewController() presentViewController:alertController animated:YES completion:NULL];
|
|
}]];
|
|
|
|
[items addObjectsFromArray:_extraMenuItems];
|
|
return items;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(show)
|
|
{
|
|
if (_actionSheet || !_bridge || RCTRunningInAppExtension()) {
|
|
return;
|
|
}
|
|
|
|
NSString *bridgeDescription = _bridge.bridgeDescription;
|
|
NSString *description = bridgeDescription.length > 0
|
|
? [NSString stringWithFormat:@"Running %@", bridgeDescription]
|
|
: nil;
|
|
|
|
// On larger devices we don't have an anchor point for the action sheet
|
|
UIAlertControllerStyle style = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert;
|
|
_actionSheet = [UIAlertController alertControllerWithTitle:@"React Native Debug Menu"
|
|
message:description
|
|
preferredStyle:style];
|
|
|
|
NSArray<RCTDevMenuItem *> *items = [self _menuItemsToPresent];
|
|
for (RCTDevMenuItem *item in items) {
|
|
[_actionSheet addAction:[UIAlertAction actionWithTitle:item.title
|
|
style:UIAlertActionStyleDefault
|
|
handler:[self alertActionHandlerForDevItem:item]]];
|
|
}
|
|
|
|
[_actionSheet addAction:[UIAlertAction actionWithTitle:@"Cancel"
|
|
style:UIAlertActionStyleCancel
|
|
handler:[self alertActionHandlerForDevItem:nil]]];
|
|
|
|
_presentedItems = items;
|
|
[RCTPresentedViewController() presentViewController:_actionSheet animated:YES completion:nil];
|
|
}
|
|
|
|
- (RCTDevMenuAlertActionHandler)alertActionHandlerForDevItem:(RCTDevMenuItem *__nullable)item
|
|
{
|
|
return ^(__unused UIAlertAction *action) {
|
|
if (item) {
|
|
[item callHandler];
|
|
}
|
|
|
|
self->_actionSheet = nil;
|
|
};
|
|
}
|
|
|
|
#pragma mark - deprecated methods and properties
|
|
|
|
#define WARN_DEPRECATED_DEV_MENU_EXPORT() RCTLogWarn(@"Using deprecated method %s, use RCTDevSettings instead", __func__)
|
|
|
|
- (void)setShakeToShow:(BOOL)shakeToShow
|
|
{
|
|
_bridge.devSettings.isShakeToShowDevMenuEnabled = shakeToShow;
|
|
}
|
|
|
|
- (BOOL)shakeToShow
|
|
{
|
|
return _bridge.devSettings.isShakeToShowDevMenuEnabled;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(reload)
|
|
{
|
|
WARN_DEPRECATED_DEV_MENU_EXPORT();
|
|
[_bridge reload];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(debugRemotely:(BOOL)enableDebug)
|
|
{
|
|
WARN_DEPRECATED_DEV_MENU_EXPORT();
|
|
_bridge.devSettings.isDebuggingRemotely = enableDebug;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setProfilingEnabled:(BOOL)enabled)
|
|
{
|
|
WARN_DEPRECATED_DEV_MENU_EXPORT();
|
|
_bridge.devSettings.isProfilingEnabled = enabled;
|
|
}
|
|
|
|
- (BOOL)profilingEnabled
|
|
{
|
|
return _bridge.devSettings.isProfilingEnabled;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setLiveReloadEnabled:(BOOL)enabled)
|
|
{
|
|
WARN_DEPRECATED_DEV_MENU_EXPORT();
|
|
_bridge.devSettings.isLiveReloadEnabled = enabled;
|
|
}
|
|
|
|
- (BOOL)liveReloadEnabled
|
|
{
|
|
return _bridge.devSettings.isLiveReloadEnabled;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setHotLoadingEnabled:(BOOL)enabled)
|
|
{
|
|
WARN_DEPRECATED_DEV_MENU_EXPORT();
|
|
_bridge.devSettings.isHotLoadingEnabled = enabled;
|
|
}
|
|
|
|
- (BOOL)hotLoadingEnabled
|
|
{
|
|
return _bridge.devSettings.isHotLoadingEnabled;
|
|
}
|
|
|
|
@end
|
|
|
|
#else // Unavailable when not in dev mode
|
|
|
|
@implementation RCTDevMenu
|
|
|
|
- (void)show {}
|
|
- (void)reload {}
|
|
- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler {}
|
|
- (void)addItem:(RCTDevMenu *)item {}
|
|
- (BOOL)isActionSheetShown { return NO; }
|
|
+ (NSString *)moduleName { return @""; }
|
|
|
|
@end
|
|
|
|
@implementation RCTDevMenuItem
|
|
|
|
+ (instancetype)buttonItemWithTitle:(NSString *)title handler:(void(^)(void))handler {return nil;}
|
|
+ (instancetype)buttonItemWithTitleBlock:(NSString * (^)(void))titleBlock
|
|
handler:(void(^)(void))handler {return nil;}
|
|
|
|
@end
|
|
|
|
#endif
|
|
|
|
@implementation RCTBridge (RCTDevMenu)
|
|
|
|
- (RCTDevMenu *)devMenu
|
|
{
|
|
#if RCT_DEV
|
|
return [self moduleForClass:[RCTDevMenu class]];
|
|
#else
|
|
return nil;
|
|
#endif
|
|
}
|
|
|
|
@end
|