From 417adf526f92edf3ccf24dfd42f91901bfad2a42 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Wed, 10 Apr 2019 11:00:50 -0700 Subject: [PATCH] Fabric iOS: allow using fallback component for unsupported ones Summary: This allows an unsupported component to be rendered as a "unimplemented view" for better visualization of which component is missing. It is off by default, but configurable in the component factory. For now, the layout simply follows regular , which means the width/height etc is based on the react component styling. The side effect is that components with 0 height/width won't show up at all. Reviewed By: mdvacca Differential Revision: D14869656 fbshipit-source-id: f31e012fb7dc1c64fcc431ea5aa45079a23a618e --- .../UnimplementedNativeView.js | 29 +++++++++ .../UnimplementedNativeViewSchema.js | 43 +++++++++++++ .../RCTUnimplementedNativeComponentView.h | 18 ++++++ .../RCTUnimplementedNativeComponentView.mm | 60 +++++++++++++++++++ .../Mounting/RCTComponentViewFactory.mm | 2 + .../uimanager/ComponentDescriptorRegistry.cpp | 20 ++++++- .../uimanager/ComponentDescriptorRegistry.h | 3 + ReactCommon/fabric/uimanager/UIManager.cpp | 11 +++- 8 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 Libraries/Components/UnimplementedViews/UnimplementedNativeView.js create mode 100644 Libraries/Components/UnimplementedViews/UnimplementedNativeViewSchema.js create mode 100644 React/Fabric/Mounting/ComponentViews/UnimplementedComponent/RCTUnimplementedNativeComponentView.h create mode 100644 React/Fabric/Mounting/ComponentViews/UnimplementedComponent/RCTUnimplementedNativeComponentView.mm diff --git a/Libraries/Components/UnimplementedViews/UnimplementedNativeView.js b/Libraries/Components/UnimplementedViews/UnimplementedNativeView.js new file mode 100644 index 00000000000..ee0b618aca5 --- /dev/null +++ b/Libraries/Components/UnimplementedViews/UnimplementedNativeView.js @@ -0,0 +1,29 @@ +/** + * 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. + * + * @format + * @flow + */ + +'use strict'; + +const requireNativeComponent = require('requireNativeComponent'); + +import type {ViewProps} from 'ViewPropTypes'; +import type {ViewStyleProp} from 'StyleSheet'; +import type {NativeComponent} from 'ReactNative'; + +type NativeProps = $ReadOnly<{| + ...ViewProps, + name?: ?string, + style?: ?ViewStyleProp, +|}>; + +type UnimplementedViewNativeType = Class>; + +module.exports = ((requireNativeComponent( + 'UnimplementedNativeView', +): any): UnimplementedViewNativeType); diff --git a/Libraries/Components/UnimplementedViews/UnimplementedNativeViewSchema.js b/Libraries/Components/UnimplementedViews/UnimplementedNativeViewSchema.js new file mode 100644 index 00000000000..99773adcc18 --- /dev/null +++ b/Libraries/Components/UnimplementedViews/UnimplementedNativeViewSchema.js @@ -0,0 +1,43 @@ +/** + * 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. + * + * @format + * @flow + */ + +'use strict'; + +import type {SchemaType} from '../../../packages/react-native-codegen/src/CodegenSchema.js'; + +const UnimplementedNativeViewSchema: SchemaType = { + modules: { + UnimplementedNativeViewSchema: { + components: { + UnimplementedNativeView: { + extendsProps: [ + { + type: 'ReactNativeBuiltInType', + knownTypeName: 'ReactNativeCoreViewProps', + }, + ], + events: [], + props: [ + { + name: 'name', + optional: true, + typeAnnotation: { + type: 'StringTypeAnnotation', + default: '', + }, + }, + ], + }, + }, + }, + }, +}; + +module.exports = UnimplementedNativeViewSchema; diff --git a/React/Fabric/Mounting/ComponentViews/UnimplementedComponent/RCTUnimplementedNativeComponentView.h b/React/Fabric/Mounting/ComponentViews/UnimplementedComponent/RCTUnimplementedNativeComponentView.h new file mode 100644 index 00000000000..0a33c7f7d66 --- /dev/null +++ b/React/Fabric/Mounting/ComponentViews/UnimplementedComponent/RCTUnimplementedNativeComponentView.h @@ -0,0 +1,18 @@ +/** + * 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 + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTUnimplementedNativeComponentView : RCTViewComponentView + +@end + +NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/ComponentViews/UnimplementedComponent/RCTUnimplementedNativeComponentView.mm b/React/Fabric/Mounting/ComponentViews/UnimplementedComponent/RCTUnimplementedNativeComponentView.mm new file mode 100644 index 00000000000..2965494db18 --- /dev/null +++ b/React/Fabric/Mounting/ComponentViews/UnimplementedComponent/RCTUnimplementedNativeComponentView.mm @@ -0,0 +1,60 @@ +/** + * 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 "RCTUnimplementedNativeComponentView.h" + +#import +#import +#import + +using namespace facebook::react; + +@implementation RCTUnimplementedNativeComponentView { + UILabel *_label; +} + +#pragma mark - RCTComponentViewProtocol + ++ (ComponentHandle)componentHandle +{ + return UnimplementedNativeViewShadowNode::Handle(); +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + + CGRect bounds = self.bounds; + _label = [[UILabel alloc] initWithFrame:bounds]; + _label.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.3]; + _label.layoutMargins = UIEdgeInsetsMake(12, 12, 12, 12); + _label.lineBreakMode = NSLineBreakByWordWrapping; + _label.numberOfLines = 0; + _label.textAlignment = NSTextAlignmentCenter; + _label.textColor = [UIColor whiteColor]; + + self.contentView = _label; + } + + return self; +} + +- (void)updateProps:(SharedProps)props oldProps:(SharedProps)oldProps +{ + const auto &oldViewProps = *std::static_pointer_cast(oldProps ?: _props); + const auto &newViewProps = *std::static_pointer_cast(props); + + [super updateProps:props oldProps:oldProps]; + + if (oldViewProps.name != newViewProps.name) { + _label.text = [NSString stringWithFormat:@"'%s' is not Fabric compatible yet.", newViewProps.name.c_str()]; + } +} + +@end diff --git a/React/Fabric/Mounting/RCTComponentViewFactory.mm b/React/Fabric/Mounting/RCTComponentViewFactory.mm index 06606d5c8e3..215bebbb696 100644 --- a/React/Fabric/Mounting/RCTComponentViewFactory.mm +++ b/React/Fabric/Mounting/RCTComponentViewFactory.mm @@ -17,6 +17,7 @@ #import "RCTScrollViewComponentView.h" #import "RCTSliderComponentView.h" #import "RCTSwitchComponentView.h" +#import "RCTUnimplementedNativeComponentView.h" #import "RCTViewComponentView.h" using namespace facebook::react; @@ -39,6 +40,7 @@ using namespace facebook::react; [componentViewFactory registerComponentViewClass:[RCTActivityIndicatorViewComponentView class]]; [componentViewFactory registerComponentViewClass:[RCTSliderComponentView class]]; [componentViewFactory registerComponentViewClass:[RCTSwitchComponentView class]]; + [componentViewFactory registerComponentViewClass:[RCTUnimplementedNativeComponentView class]]; return componentViewFactory; } diff --git a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp index 5dbcbda2ad2..2a8f129c7d0 100644 --- a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp +++ b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp @@ -92,9 +92,12 @@ const ComponentDescriptor &ComponentDescriptorRegistry::at( auto it = _registryByName.find(unifiedComponentName); if (it == _registryByName.end()) { - throw std::invalid_argument( - ("Unable to find componentDescriptor for " + unifiedComponentName) - .c_str()); + if (_fallbackComponentDescriptor == nullptr) { + throw std::invalid_argument( + ("Unable to find componentDescriptor for " + unifiedComponentName) + .c_str()); + } + return *_fallbackComponentDescriptor.get(); } return *it->second; } @@ -123,5 +126,16 @@ SharedShadowNode ComponentDescriptorRegistry::createNode( return shadowNode; } +void ComponentDescriptorRegistry::setFallbackComponentDescriptor( + SharedComponentDescriptor descriptor) { + _fallbackComponentDescriptor = descriptor; + registerComponentDescriptor(descriptor); +} + +const SharedComponentDescriptor +ComponentDescriptorRegistry::getFallbackComponentDescriptor() const { + return _fallbackComponentDescriptor; +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h index 9555c063ef9..7469da34110 100644 --- a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h +++ b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h @@ -39,10 +39,13 @@ class ComponentDescriptorRegistry { Tag rootTag, const folly::dynamic &props, const SharedEventTarget &eventTarget) const; + void setFallbackComponentDescriptor(SharedComponentDescriptor descriptor); + const SharedComponentDescriptor getFallbackComponentDescriptor() const; private: better::map _registryByHandle; better::map _registryByName; + SharedComponentDescriptor _fallbackComponentDescriptor; }; } // namespace react diff --git a/ReactCommon/fabric/uimanager/UIManager.cpp b/ReactCommon/fabric/uimanager/UIManager.cpp index dc2df34ab85..f2ed31105ad 100644 --- a/ReactCommon/fabric/uimanager/UIManager.cpp +++ b/ReactCommon/fabric/uimanager/UIManager.cpp @@ -18,13 +18,22 @@ SharedShadowNode UIManager::createNode( SystraceSection s("UIManager::createNode"); auto &componentDescriptor = componentDescriptorRegistry_->at(name); + auto fallbackDescriptor = + componentDescriptorRegistry_->getFallbackComponentDescriptor(); + const auto &props = componentDescriptor.cloneProps(nullptr, rawProps); const auto &state = componentDescriptor.createInitialState(props); auto shadowNode = componentDescriptor.createShadowNode({ /* .tag = */ tag, /* .rootTag = */ surfaceId, - /* .props = */ props, + /* .props = */ + fallbackDescriptor != nullptr && + fallbackDescriptor->getComponentHandle() == + componentDescriptor.getComponentHandle() + ? componentDescriptor.cloneProps( + nullptr, RawProps(folly::dynamic::object("name", name))) + : props, /* .eventEmitter = */ componentDescriptor.createEventEmitter(std::move(eventTarget), tag), /* .children = */ ShadowNodeFragment::childrenPlaceholder(),