diff --git a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js index cddb1302d754..40b168372af7 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js +++ b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js @@ -36,8 +36,12 @@ extern "C" { Class RCTThirdPartyFabricComponentsProvider(const char *name); +#ifndef RCT_DYNAMIC_FRAMEWORKS + ${lookupFuncs} +#endif + #ifdef __cplusplus } #endif diff --git a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js index fcd7b5281926..06e964845d3f 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js +++ b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js @@ -34,7 +34,9 @@ const FileTemplate = ({lookupMap}: {lookupMap: string}) => ` Class RCTThirdPartyFabricComponentsProvider(const char *name) { static std::unordered_map sFabricComponentsClassMap = { + #ifndef RCT_DYNAMIC_FRAMEWORKS ${lookupMap} + #endif }; auto p = sFabricComponentsClassMap.find(name); diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap index 82f6247e94f4..f1b25981b317 100644 --- a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap @@ -23,6 +23,8 @@ extern \\"C\\" { Class RCTThirdPartyFabricComponentsProvider(const char *name); +#ifndef RCT_DYNAMIC_FRAMEWORKS + Class NoPropsNoEventsComponentCls(void) __attribute__((used)); // NO_PROPS_NO_EVENTS Class InterfaceOnlyComponentCls(void) __attribute__((used)); // INTERFACE_ONLY Class BooleanPropNativeComponentCls(void) __attribute__((used)); // BOOLEAN_PROP @@ -55,6 +57,8 @@ Class ExcludedAndroidComponentCls(void) __attribute__( Class MultiFileIncludedNativeComponentCls(void) __attribute__((used)); // EXCLUDE_IOS_TWO_COMPONENTS_DIFFERENT_FILES +#endif + #ifdef __cplusplus } #endif diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap index c766a6b679a9..eb6d622917a2 100644 --- a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap @@ -21,6 +21,7 @@ Map { Class RCTThirdPartyFabricComponentsProvider(const char *name) { static std::unordered_map sFabricComponentsClassMap = { + #ifndef RCT_DYNAMIC_FRAMEWORKS {\\"NoPropsNoEventsComponent\\", NoPropsNoEventsComponentCls}, // NO_PROPS_NO_EVENTS @@ -80,6 +81,7 @@ Class RCTThirdPartyFabricComponentsProvider(const char {\\"MultiFileIncludedNativeComponent\\", MultiFileIncludedNativeComponentCls}, // EXCLUDE_IOS_TWO_COMPONENTS_DIFFERENT_FILES + #endif }; auto p = sFabricComponentsClassMap.find(name); diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h index 93b1543c7bac..2eb02eafb85d 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h @@ -9,6 +9,7 @@ #import #import +@protocol RCTComponentViewProtocol; @class RCTSurfacePresenterBridgeAdapter; /** @@ -96,6 +97,14 @@ #if RCT_NEW_ARCH_ENABLED @property (nonatomic, strong) RCTSurfacePresenterBridgeAdapter *bridgeAdapter; +/// This method returns a map of Component Descriptors and Components classes that needs to be registered in the +/// new renderer. The Component Descriptor is a string which represent the name used in JS to refer to the native +/// component. The default implementation returns an empty dictionary. Subclasses can override this method to register +/// the required components. +/// +/// @return a dictionary that associate a component for the new renderer with his descriptor. +- (NSDictionary> *)thirdPartyFabricComponents; + /// This method controls whether the `turboModules` feature of the New Architecture is turned on or off. /// /// @note: This is required to be rendering on Fabric (i.e. on the New Architecture). diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index fffea76a6c34..c04d5e3fed06 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -11,6 +11,8 @@ #if RCT_NEW_ARCH_ENABLED #import +#import +#import #import #import #import @@ -24,7 +26,10 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; -@interface RCTAppDelegate () { +@interface RCTAppDelegate () < + RCTTurboModuleManagerDelegate, + RCTCxxBridgeDelegate, + RCTComponentViewFactoryComponentProvider> { std::shared_ptr _reactNativeConfig; facebook::react::ContextContainer::Shared _contextContainer; std::shared_ptr _runtimeScheduler; @@ -64,6 +69,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter; [self unstable_registerLegacyComponents]; + [RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self; #endif NSDictionary *initProps = [self prepareInitialProps]; @@ -163,6 +169,13 @@ - (Class)getModuleClassFromName:(const char *)name return RCTAppSetupDefaultModuleFromClass(moduleClass); } +#pragma mark - RCTComponentViewFactoryComponentProvider + +- (NSDictionary> *)thirdPartyFabricComponents +{ + return @{}; +} + #pragma mark - New Arch Enabled settings - (BOOL)turboModuleEnabled diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 60e0697071d8..e2239c995315 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -18,6 +18,10 @@ #import #import +#ifdef RCT_DYNAMIC_FRAMEWORKS +#import +#endif + using namespace facebook::react; @implementation RCTViewComponentView { @@ -30,6 +34,13 @@ @implementation RCTViewComponentView { NSSet *_Nullable _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN; } +#ifdef RCT_DYNAMIC_FRAMEWORKS ++ (void)load +{ + [RCTComponentViewFactory.currentComponentViewFactory registerComponentViewClass:self]; +} +#endif + - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { diff --git a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.h b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.h index 771131d50f44..15eb8f0b4c8b 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.h +++ b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.h @@ -16,12 +16,28 @@ NS_ASSUME_NONNULL_BEGIN void RCTInstallNativeComponentRegistryBinding(facebook::jsi::Runtime &runtime); +/** + * Protocol that can be implemented to provide some 3rd party components to Fabric. + * Fabric will check in this map whether there are some components that need to be registered. + */ +@protocol RCTComponentViewFactoryComponentProvider + +/** + * Return a dictionary of third party components where the `key` is the Component Handler and the `value` is a Class + * that conforms to `RCTComponentViewProtocol`. + */ +- (NSDictionary> *)thirdPartyFabricComponents; + +@end + /** * Registry of supported component view classes that can instantiate * view component instances by given component handle. */ @interface RCTComponentViewFactory : NSObject +@property (nonatomic, weak) id thirdPartyFabricComponentsProvider; + /** * Constructs and returns an instance of the class with a bunch of already registered standard components. */ diff --git a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm index 216c2c0b3329..8ed6f9a8319d 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm +++ b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm @@ -112,7 +112,19 @@ - (BOOL)registerComponentIfPossible:(std::string const &)name return YES; } - // Fallback 2: Try to use Paper Interop. + // Fallback 2: Ask the provider and check in the dictionary provided + if (self.thirdPartyFabricComponentsProvider) { + // Test whether a provider has been passed to avoid potentially expensive conversions + // between C++ and ObjC strings. + NSString *objcName = [NSString stringWithCString:name.c_str() encoding:NSUTF8StringEncoding]; + klass = self.thirdPartyFabricComponentsProvider.thirdPartyFabricComponents[objcName]; + if (klass) { + [self registerComponentViewClass:klass]; + return YES; + } + } + + // Fallback 3: Try to use Paper Interop. NSString *componentNameString = RCTNSStringFromString(name); if ([RCTLegacyViewManagerInteropComponentView isSupported:componentNameString]) { RCTLogNewArchitectureValidation( @@ -136,7 +148,7 @@ - (BOOL)registerComponentIfPossible:(std::string const &)name return YES; } - // Fallback 3: use if component doesn't exist. + // Fallback 4: use if component doesn't exist. auto flavor = std::make_shared(name); auto componentName = ComponentName{flavor->c_str()}; auto componentHandle = reinterpret_cast(componentName); diff --git a/packages/react-native/scripts/cocoapods/new_architecture.rb b/packages/react-native/scripts/cocoapods/new_architecture.rb index 66aea0698159..a6eac31d9c58 100644 --- a/packages/react-native/scripts/cocoapods/new_architecture.rb +++ b/packages/react-native/scripts/cocoapods/new_architecture.rb @@ -67,6 +67,16 @@ def self.modify_flags_for_new_architecture(installer, is_new_arch_enabled) end end + # Set "RCT_DYNAMIC_FRAMEWORKS=1" if pod are installed with USE_FRAMEWORKS=dynamic + # This helps with backward compatibility. + if pod_name == 'React-RCTFabric' && ENV['USE_FRAMEWORKS'] == 'dynamic' + rct_dynamic_framework_flag = " -DRCT_DYNAMIC_FRAMEWORKS=1" + target_installation_result.native_target.build_configurations.each do |config| + prev_build_settings = config.build_settings['OTHER_CPLUSPLUSFLAGS'] != nil ? config.build_settings['OTHER_CPLUSPLUSFLAGS'] : "$(inherithed)" + config.build_settings['OTHER_CPLUSPLUSFLAGS'] = prev_build_settings + rct_dynamic_framework_flag + end + end + target_installation_result.native_target.build_configurations.each do |config| if config.name == "Release" current_flags = config.build_settings['OTHER_CPLUSPLUSFLAGS'] != nil ? config.build_settings['OTHER_CPLUSPLUSFLAGS'] : "$(inherited)" diff --git a/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm b/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm index 33e7a7f2e8b2..5e14c832f9fb 100644 --- a/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm +++ b/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm @@ -28,6 +28,14 @@ + (ComponentDescriptorProvider)componentDescriptorProvider return concreteComponentDescriptorProvider(); } +// Load is not invoked if it is not defined, therefore, we must ask to update this. +// See the Apple documentation: https://developer.apple.com/documentation/objectivec/nsobject/1418815-load?language=objc +// "[...] but only if the newly loaded class or category implements a method that can respond." ++ (void)load +{ + [super load]; +} + - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { diff --git a/packages/rn-tester/RNTester/AppDelegate.mm b/packages/rn-tester/RNTester/AppDelegate.mm index d3f3f355f948..bb1cbe68f73e 100644 --- a/packages/rn-tester/RNTester/AppDelegate.mm +++ b/packages/rn-tester/RNTester/AppDelegate.mm @@ -49,6 +49,7 @@ #endif #ifdef RN_FABRIC_ENABLED +#import #import #import #import @@ -60,6 +61,10 @@ #import #endif +#if RCT_NEW_ARCH_ENABLED +#import +#endif + #if DEBUG #ifdef FB_SONARKIT_ENABLED #import @@ -85,6 +90,12 @@ @interface AppDelegate () } @end +#if RCT_NEW_ARCH_ENABLED +/// Declare conformance to `RCTComponentViewFactoryComponentProvider` +@interface AppDelegate () +@end +#endif + static NSString *const kRNConcurrentRoot = @"concurrentRoot"; @implementation AppDelegate @@ -110,6 +121,10 @@ - (BOOL)application:(__unused UIApplication *)application didFinishLaunchingWith // Appetizer.io params check NSDictionary *initProps = [self prepareInitialProps]; +#if RCT_NEW_ARCH_ENABLED + [RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self; +#endif + #ifdef RN_FABRIC_ENABLED _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:_bridge contextContainer:_contextContainer]; @@ -333,4 +348,13 @@ - (void)application:(__unused UIApplication *)application #endif +#pragma mark - RCTComponentViewFactoryComponentProvider + +#if RCT_NEW_ARCH_ENABLED +- (nonnull NSDictionary> *)thirdPartyFabricComponents +{ + return @{@"RNTMyNativeView" : RNTMyNativeViewComponentView.class}; +} +#endif + @end