mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Make UIManager.dispatchViewManagerCommand work in Fabric through Interop Layer (#36578)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36578 This change brings to the Fabric Interop Layer the possibility to directly call native methods on the View Manager, something that was not possible before and that we can use to simplify the migration to the New Architecture. [iOS][Added] - Native Components can now call native methods in Fabric when they are loaded through the Interop Layer Reviewed By: sammy-SC Differential Revision: D43945278 fbshipit-source-id: fe67ac85a5d0db3747105f56700d1dbba7ada5f1
This commit is contained in:
committed by
Riccardo Cipolleschi
parent
18137348ee
commit
3e079f0802
@@ -175,6 +175,33 @@ const UIManager = {
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
dispatchViewManagerCommand(
|
||||
reactTag: number,
|
||||
commandName: number | string,
|
||||
commandArgs: any[],
|
||||
) {
|
||||
if (isFabricReactTag(reactTag)) {
|
||||
const FabricUIManager = nullthrows(getFabricUIManager());
|
||||
const shadowNode =
|
||||
FabricUIManager.findShadowNodeByTag_DEPRECATED(reactTag);
|
||||
if (shadowNode) {
|
||||
// Transform the accidental CommandID into a CommandName which is the stringified number.
|
||||
// The interop layer knows how to convert this number into the right method name.
|
||||
// Stringify a string is a no-op, so it's safe.
|
||||
commandName = `${commandName}`;
|
||||
FabricUIManager.dispatchCommand(shadowNode, commandName, commandArgs);
|
||||
}
|
||||
} else {
|
||||
UIManagerImpl.dispatchViewManagerCommand(
|
||||
reactTag,
|
||||
// We have some legacy components that are actually already using strings. ¯\_(ツ)_/¯
|
||||
// $FlowFixMe[incompatible-call]
|
||||
commandName,
|
||||
commandArgs,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = UIManager;
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@
|
||||
|
||||
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args
|
||||
{
|
||||
[_coordinator handleCommand:commandName args:args reactTag:_tag];
|
||||
[_coordinator handleCommand:commandName args:args reactTag:_tag paperView:self.paperView];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+4
-1
@@ -33,7 +33,10 @@ typedef void (^InterceptorBlock)(std::string eventName, folly::dynamic event);
|
||||
|
||||
- (NSString *)componentViewName;
|
||||
|
||||
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args reactTag:(NSInteger)tag;
|
||||
- (void)handleCommand:(NSString *)commandName
|
||||
args:(NSArray *)args
|
||||
reactTag:(NSInteger)tag
|
||||
paperView:(UIView *)paperView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+49
-2
@@ -100,13 +100,23 @@ using namespace facebook::react;
|
||||
return RCTDropReactPrefixes(_componentData.name);
|
||||
}
|
||||
|
||||
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args reactTag:(NSInteger)tag
|
||||
- (void)handleCommand:(NSString *)commandName
|
||||
args:(NSArray *)args
|
||||
reactTag:(NSInteger)tag
|
||||
paperView:(nonnull UIView *)paperView
|
||||
{
|
||||
Class managerClass = _componentData.managerClass;
|
||||
[self _lookupModuleMethodsIfNecessary];
|
||||
RCTModuleData *moduleData = [_bridge.batchedBridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
|
||||
id<RCTBridgeMethod> method;
|
||||
if ([commandName isKindOfClass:[NSNumber class]]) {
|
||||
|
||||
// We can't use `[NSString intValue]` as "0" is a valid command,
|
||||
// but also a falsy value. [NSNumberFormatter numberFromString] returns a
|
||||
// `NSNumber *` which is NULL when it's to be NULL
|
||||
// and it points to 0 when the string is @"0" (not a falsy value).
|
||||
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
||||
|
||||
if ([commandName isKindOfClass:[NSNumber class]] || [formatter numberFromString:commandName] != NULL) {
|
||||
method = moduleData ? moduleData.methods[[commandName intValue]] : _moduleMethods[[commandName intValue]];
|
||||
} else if ([commandName isKindOfClass:[NSString class]]) {
|
||||
method = moduleData ? moduleData.methodsByName[commandName] : _moduleMethodsByName[commandName];
|
||||
@@ -121,12 +131,14 @@ using namespace facebook::react;
|
||||
NSArray *newArgs = [@[ [NSNumber numberWithInteger:tag] ] arrayByAddingObjectsFromArray:args];
|
||||
|
||||
if (_bridge) {
|
||||
[self _addViewToRegistry:paperView withTag:tag];
|
||||
[_bridge.batchedBridge
|
||||
dispatchBlock:^{
|
||||
[method invokeWithBridge:self->_bridge module:self->_componentData.manager arguments:newArgs];
|
||||
[self->_bridge.uiManager setNeedsLayout];
|
||||
}
|
||||
queue:RCTGetUIManagerQueue()];
|
||||
[self _removeViewFromRegistryWithTag:tag];
|
||||
} else {
|
||||
// TODO T86826778 - Figure out which queue this should be dispatched to.
|
||||
[method invokeWithBridge:nil module:self->_componentData.manager arguments:newArgs];
|
||||
@@ -134,6 +146,41 @@ using namespace facebook::react;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (void)_addViewToRegistry:(UIView *)view withTag:(NSInteger)tag
|
||||
{
|
||||
[self _addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
if ([viewRegistry objectForKey:@(tag)] != NULL) {
|
||||
return;
|
||||
}
|
||||
NSMutableDictionary<NSNumber *, UIView *> *mutableViewRegistry =
|
||||
(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry;
|
||||
[mutableViewRegistry setObject:view forKey:@(tag)];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_removeViewFromRegistryWithTag:(NSInteger)tag
|
||||
{
|
||||
[self _addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
if ([viewRegistry objectForKey:@(tag)] == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary<NSNumber *, UIView *> *mutableViewRegistry =
|
||||
(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry;
|
||||
[mutableViewRegistry removeObjectForKey:@(tag)];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_addUIBlock:(RCTViewManagerUIBlock)block
|
||||
{
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
[_bridge.batchedBridge
|
||||
dispatchBlock:^{
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
[strongSelf->_bridge.uiManager addUIBlock:block];
|
||||
}
|
||||
queue:RCTGetUIManagerQueue()];
|
||||
}
|
||||
|
||||
// This is copy-pasta from RCTModuleData.
|
||||
- (void)_lookupModuleMethodsIfNecessary
|
||||
|
||||
@@ -30,9 +30,34 @@ RCT_REMAP_VIEW_PROPERTY(opacity, alpha, CGFloat)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(onColorChanged, RCTBubblingEventBlock)
|
||||
|
||||
RCT_EXPORT_METHOD(changeBackgroundColor : (nonnull NSNumber *)reactTag color : (NSString *)color)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if (!view || ![view isKindOfClass:[RNTLegacyView class]]) {
|
||||
RCTLogError(@"Cannot find RNTLegacyView with tag #%@", reactTag);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned rgbValue = 0;
|
||||
NSString *colorString = [NSString stringWithCString:std::string([color UTF8String]).c_str()
|
||||
encoding:[NSString defaultCStringEncoding]];
|
||||
NSScanner *scanner = [NSScanner scannerWithString:colorString];
|
||||
[scanner setScanLocation:1]; // bypass '#' character
|
||||
[scanner scanHexInt:&rgbValue];
|
||||
|
||||
UIColor *newColor = [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0
|
||||
green:((rgbValue & 0xFF00) >> 8) / 255.0
|
||||
blue:(rgbValue & 0xFF) / 255.0
|
||||
alpha:1.0];
|
||||
view.backgroundColor = newColor;
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
RNTLegacyView *view = [[RNTLegacyView alloc] init];
|
||||
view.backgroundColor = UIColor.redColor;
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import type {HostComponent} from 'react-native';
|
||||
import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes';
|
||||
import {requireNativeComponent} from 'react-native';
|
||||
import {requireNativeComponent, UIManager} from 'react-native';
|
||||
import ReactNative from '../../../react-native/Libraries/Renderer/shims/ReactNative';
|
||||
|
||||
type ColorChangedEvent = {
|
||||
nativeEvent: {
|
||||
@@ -32,6 +34,22 @@ type NativeProps = $ReadOnly<{|
|
||||
|
||||
export type MyLegacyViewType = HostComponent<NativeProps>;
|
||||
|
||||
export function callNativeMethodToChangeBackgroundColor(
|
||||
viewRef: React.ElementRef<MyLegacyViewType> | null,
|
||||
color: string,
|
||||
) {
|
||||
if (!viewRef) {
|
||||
console.log('viewRef is null');
|
||||
return;
|
||||
}
|
||||
UIManager.dispatchViewManagerCommand(
|
||||
ReactNative.findNodeHandle(viewRef),
|
||||
UIManager.getViewManagerConfig('RNTMyLegacyNativeView').Commands
|
||||
.changeBackgroundColor,
|
||||
[color],
|
||||
);
|
||||
}
|
||||
|
||||
export default (requireNativeComponent(
|
||||
'RNTMyLegacyNativeView',
|
||||
): HostComponent<NativeProps>);
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import {useRef, useState} from 'react';
|
||||
import {View, Button, Text} from 'react-native';
|
||||
import {View, Button, Text, UIManager} from 'react-native';
|
||||
import RNTMyNativeView, {
|
||||
Commands as RNTMyNativeViewCommands,
|
||||
} from './MyNativeViewNativeComponent';
|
||||
import RNTMyLegacyNativeView from './MyLegacyViewNativeComponent';
|
||||
import type {MyLegacyViewType} from './MyLegacyViewNativeComponent';
|
||||
import type {MyNativeViewType} from './MyNativeViewNativeComponent';
|
||||
import {UIManager} from 'react-native';
|
||||
import {callNativeMethodToChangeBackgroundColor} from './MyLegacyViewNativeComponent';
|
||||
|
||||
const colors = [
|
||||
'#0000FF',
|
||||
@@ -53,8 +54,8 @@ class HSBA {
|
||||
// This is an example component that migrates to use the new architecture.
|
||||
export default function MyNativeView(props: {}): React.Node {
|
||||
const ref = useRef<React.ElementRef<MyNativeViewType> | null>(null);
|
||||
const legacyRef = useRef<React.ElementRef<MyLegacyViewType> | null>(null);
|
||||
const [opacity, setOpacity] = useState(1.0);
|
||||
const [color, setColor] = useState('#FF0000');
|
||||
const [hsba, setHsba] = useState<HSBA>(new HSBA());
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
@@ -62,9 +63,9 @@ export default function MyNativeView(props: {}): React.Node {
|
||||
<RNTMyNativeView ref={ref} style={{flex: 1}} opacity={opacity} />
|
||||
<Text style={{color: 'red'}}>Legacy View</Text>
|
||||
<RNTMyLegacyNativeView
|
||||
ref={legacyRef}
|
||||
style={{flex: 1}}
|
||||
opacity={opacity}
|
||||
color={color}
|
||||
onColorChanged={event =>
|
||||
setHsba(
|
||||
new HSBA(
|
||||
@@ -85,12 +86,13 @@ export default function MyNativeView(props: {}): React.Node {
|
||||
title="Change Background"
|
||||
onPress={() => {
|
||||
let newColor = colors[Math.floor(Math.random() * 5)];
|
||||
setColor(newColor);
|
||||
RNTMyNativeViewCommands.callNativeMethodToChangeBackgroundColor(
|
||||
// $FlowFixMe[incompatible-call]
|
||||
ref.current,
|
||||
newColor,
|
||||
);
|
||||
|
||||
callNativeMethodToChangeBackgroundColor(legacyRef.current, newColor);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user