From 47557ae781a21ba5bfc4ea586a4f3fcdd0267256 Mon Sep 17 00:00:00 2001 From: Dmitry Rykun Date: Tue, 18 Jul 2023 08:15:13 -0700 Subject: [PATCH] iOS: Add Commands and Constants properties to native view config (#38221) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38221 View configs are supposed to have `Commands` and `Constants`. See [ReactNativeTypes.js](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js#L68-L69). Android sets them in native: see [UIManagerModuleConstantsHelper.java](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java#L172-L179). ut iOS doesn't: see [RCTUIManager.m](https://github.com/facebook/react-native/blob/main/packages/react-native/React/Modules/RCTUIManager.m#L1484-L1487) Instead there is code for that in [PaperUIManager.js](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/ReactNative/PaperUIManager.js#L117-L160). It accesses viewManagers like this: `const viewManager = NativeModules[viewConfig.Manager]`. But `NativeModules` object is not available in the bridgeless mode. So we fail to provide complete native view configs in the New Architecture. This diff implements `Commands` and `Constants` in native in iOS. This change should have no effect in the old architecture because these properties are overwritten by `lazifyViewManagerConfig`. Changelog: [Internal] - Add Commands and Constants properties to native view config. Reviewed By: sammy-SC Differential Revision: D47096624 fbshipit-source-id: e3b3183ba5e3d1d2fb3f3ff5d6ff89ad86095a6c --- .../react-native/React/Modules/RCTUIManager.m | 7 ++++ .../React/Views/RCTComponentData.m | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/packages/react-native/React/Modules/RCTUIManager.m b/packages/react-native/React/Modules/RCTUIManager.m index 6f03df143f1..209c1326e00 100644 --- a/packages/react-native/React/Modules/RCTUIManager.m +++ b/packages/react-native/React/Modules/RCTUIManager.m @@ -1485,6 +1485,13 @@ NSMutableDictionary *RCTModuleConstantsForDestructuredComponent( moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"]; moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes; moduleConstants[@"directEventTypes"] = directEventTypes; + // In the Old Architecture the "Commands" and "Constants" properties of view manager config are populated by + // lazifyViewManagerConfig function in JS. This fuction uses NativeModules global object that is not available in the + // New Architecture. To make native view configs work in the New Architecture we will populate these properties in + // native. + moduleConstants[@"Commands"] = viewConfig[@"Commands"]; + // In the Old Architecture "Constants" are empty. + moduleConstants[@"Constants"] = [NSDictionary new]; // Add direct events for (NSString *eventName in viewConfig[@"directEvents"]) { diff --git a/packages/react-native/React/Views/RCTComponentData.m b/packages/react-native/React/Views/RCTComponentData.m index 94b30a63480..da9f8206138 100644 --- a/packages/react-native/React/Views/RCTComponentData.m +++ b/packages/react-native/React/Views/RCTComponentData.m @@ -15,6 +15,7 @@ #import "RCTConstants.h" #import "RCTConvert.h" #import "RCTEventDispatcherProtocol.h" +#import "RCTModuleMethod.h" #import "RCTParserUtils.h" #import "RCTShadowView.h" #import "RCTUtils.h" @@ -385,6 +386,34 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S }]; } ++ (NSDictionary *)commandsForViewMangerClass:(Class)managerClass + methods:(Method *)methods + methodCount:(unsigned int)methodCount +{ + NSMutableDictionary *commands = [NSMutableDictionary new]; + static const char *prefix = "__rct_export__"; + const unsigned int prefixLength = strlen(prefix); + int commandCount = 0; + for (int i = 0; i < methodCount; i++) { + SEL selector = method_getName(methods[i]); + const char *selectorName = sel_getName(selector); + if (strncmp(selectorName, prefix, prefixLength) != 0) { + continue; + } + RCTMethodInfo *methodInfo = ((RCTMethodInfo * (*)(id, SEL)) objc_msgSend)(managerClass, selector); + RCTModuleMethod *moduleMethod = [[RCTModuleMethod alloc] initWithExportedMethod:methodInfo + moduleClass:managerClass]; + NSString *methodName = @(moduleMethod.JSMethodName); + commands[methodName] = @(commandCount); + commandCount += 1; + } + // View manager do not export getConstants with RCT_EXPORT_METHOD, so we inject it into "Commands" manually. + if (commandCount > 0) { + commands[@"getConstants"] = @(commandCount); + } + return commands; +} + + (NSDictionary *)viewConfigForViewMangerClass:(Class)managerClass { NSMutableArray *bubblingEvents = [NSMutableArray new]; @@ -442,6 +471,10 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S propTypes[name] = type; } } + + NSDictionary *commands = [self commandsForViewMangerClass:managerClass + methods:methods + methodCount:count]; free(methods); #if RCT_DEBUG @@ -464,6 +497,7 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S @"bubblingEvents" : bubblingEvents, @"capturingEvents" : capturingEvents, @"baseModuleName" : superClass == [NSObject class] ? (id)kCFNull : RCTViewManagerModuleNameForClass(superClass), + @"Commands" : commands, }; }