From e8b2145263092fc982e40c964e73fd997579cf04 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Sun, 19 May 2019 17:40:45 -0700 Subject: [PATCH] Fabric: Standard PullToRefresh component Summary: This is implementation of standard PullToRefresh component that uses standard iOS component and modern integration approach. Reviewed By: mdvacca Differential Revision: D15403308 fbshipit-source-id: 5c877f7c18af9f5ac40e15a4ba44118614ba80bc --- .../RNPullToRefreshViewComponentView.h | 23 +++ .../RNPullToRefreshViewComponentView.mm | 142 ++++++++++++++++++ .../Mounting/RCTComponentViewFactory.mm | 2 + .../PullToRefreshViewComponentDescriptor.h | 20 +++ .../PullToRefreshViewEventEmitter.cpp | 18 +++ .../PullToRefreshViewEventEmitter.h | 25 +++ .../scrollview/PullToRefreshViewProps.cpp | 28 ++++ .../scrollview/PullToRefreshViewProps.h | 31 ++++ .../PullToRefreshViewShadowNode.cpp | 16 ++ .../scrollview/PullToRefreshViewShadowNode.h | 31 ++++ .../uimanager/ComponentDescriptorRegistry.cpp | 3 +- 11 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 React/Fabric/Mounting/ComponentViews/ScrollView/RNPullToRefreshViewComponentView.h create mode 100644 React/Fabric/Mounting/ComponentViews/ScrollView/RNPullToRefreshViewComponentView.mm create mode 100644 ReactCommon/fabric/components/scrollview/PullToRefreshViewComponentDescriptor.h create mode 100644 ReactCommon/fabric/components/scrollview/PullToRefreshViewEventEmitter.cpp create mode 100644 ReactCommon/fabric/components/scrollview/PullToRefreshViewEventEmitter.h create mode 100644 ReactCommon/fabric/components/scrollview/PullToRefreshViewProps.cpp create mode 100644 ReactCommon/fabric/components/scrollview/PullToRefreshViewProps.h create mode 100644 ReactCommon/fabric/components/scrollview/PullToRefreshViewShadowNode.cpp create mode 100644 ReactCommon/fabric/components/scrollview/PullToRefreshViewShadowNode.h diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RNPullToRefreshViewComponentView.h b/React/Fabric/Mounting/ComponentViews/ScrollView/RNPullToRefreshViewComponentView.h new file mode 100644 index 0000000000..98d6040f95 --- /dev/null +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RNPullToRefreshViewComponentView.h @@ -0,0 +1,23 @@ +/** + * 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 + +/* + * UIView class for root component. + * This view is designed to only serve ViewController-like purpose for the actual `UIRefreshControl` view which is being + * attached to some `UIScrollView` (not to this view). + */ +@interface RNPullToRefreshViewComponentView : RCTViewComponentView + +@end + +NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RNPullToRefreshViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RNPullToRefreshViewComponentView.mm new file mode 100644 index 0000000000..2ab27b56f7 --- /dev/null +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RNPullToRefreshViewComponentView.mm @@ -0,0 +1,142 @@ +/** + * 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 "RNPullToRefreshViewComponentView.h" + +#import +#import +#import + +#import +#import + + +using namespace facebook::react; + +@implementation RNPullToRefreshViewComponentView { + UIRefreshControl *_refreshControl; + RCTScrollViewComponentView *_scrollViewComponentView; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + // This view is not designed to be visible, it only serves UIViewController-like purpose managing + // attaching and detaching of a pull-to-refresh view to a scroll view. + // The pull-to-refresh view is not a subview of this view. + self.hidden = YES; + + static auto const defaultProps = std::make_shared(); + _props = defaultProps; + + _refreshControl = [[UIRefreshControl alloc] init]; + [_refreshControl addTarget:self action:@selector(handleUIControlEventValueChanged) forControlEvents:UIControlEventValueChanged]; + } + + return self; +} + +#pragma mark - RCTComponentViewProtocol + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +- (void)updateProps:(SharedProps)props oldProps:(SharedProps)oldProps +{ + auto const &oldConcreteProps = *std::static_pointer_cast(oldProps ?: _props); + auto const &newConcreteProps = *std::static_pointer_cast(props); + + [super updateProps:props oldProps:oldProps]; + + if (newConcreteProps.refreshing != oldConcreteProps.refreshing) { + if (newConcreteProps.refreshing) { + [_refreshControl beginRefreshing]; + } else { + [_refreshControl endRefreshing]; + } + } + + BOOL needsUpdateTitle = NO; + + if (newConcreteProps.title != oldConcreteProps.title) { + needsUpdateTitle = YES; + } + + if (newConcreteProps.titleColor != oldConcreteProps.titleColor) { + needsUpdateTitle = YES; + } + + if (needsUpdateTitle) { + [self _updateTitle]; + } +} + +#pragma mark - + +- (void)handleUIControlEventValueChanged +{ + std::static_pointer_cast(_eventEmitter)->onRefresh(); +} + +- (void)_updateTitle +{ + auto const &concreteProps = *std::static_pointer_cast(_props); + + if (concreteProps.title.empty()) { + _refreshControl.attributedTitle = nil; + return; + } + + NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; + if (concreteProps.titleColor) { + attributes[NSForegroundColorAttributeName] = RCTUIColorFromSharedColor(concreteProps.titleColor); + } + + _refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:RCTNSStringFromString(concreteProps.title) attributes:attributes]; +} + +#pragma mark - Attaching & Detaching + +- (void)didMoveToWindow +{ + if (self.window) { + [self _attach]; + } else { + [self _detach]; + } +} + +- (void)_attach +{ + if (_scrollViewComponentView) { + [self _detach]; + } + + _scrollViewComponentView = [RCTScrollViewComponentView findScrollViewComponentViewForView:self]; + if (!_scrollViewComponentView) { + return; + } + + _scrollViewComponentView.scrollView.refreshControl = _refreshControl; +} + +- (void)_detach +{ + if (!_scrollViewComponentView) { + return; + } + + // iOS requires to end refreshing before unmounting. + [_refreshControl endRefreshing]; + + _scrollViewComponentView.scrollView.refreshControl = nil; + _scrollViewComponentView = nil; +} + +@end diff --git a/React/Fabric/Mounting/RCTComponentViewFactory.mm b/React/Fabric/Mounting/RCTComponentViewFactory.mm index 75bd68e630..013324402c 100644 --- a/React/Fabric/Mounting/RCTComponentViewFactory.mm +++ b/React/Fabric/Mounting/RCTComponentViewFactory.mm @@ -23,6 +23,7 @@ #import "RCTSwitchComponentView.h" #import "RCTUnimplementedNativeComponentView.h" #import "RCTViewComponentView.h" +#import "RNPullToRefreshViewComponentView.h" using namespace facebook::react; @@ -39,6 +40,7 @@ using namespace facebook::react; [componentViewFactory registerComponentViewClass:[RCTViewComponentView class]]; [componentViewFactory registerComponentViewClass:[RCTRootComponentView class]]; [componentViewFactory registerComponentViewClass:[RCTScrollViewComponentView class]]; + [componentViewFactory registerComponentViewClass:[RNPullToRefreshViewComponentView class]]; [componentViewFactory registerComponentViewClass:[RCTImageComponentView class]]; [componentViewFactory registerComponentViewClass:[RCTParagraphComponentView class]]; [componentViewFactory registerComponentViewClass:[RCTActivityIndicatorViewComponentView class]]; diff --git a/ReactCommon/fabric/components/scrollview/PullToRefreshViewComponentDescriptor.h b/ReactCommon/fabric/components/scrollview/PullToRefreshViewComponentDescriptor.h new file mode 100644 index 0000000000..4e9bcd039d --- /dev/null +++ b/ReactCommon/fabric/components/scrollview/PullToRefreshViewComponentDescriptor.h @@ -0,0 +1,20 @@ +/** + * 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. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +using PullToRefreshViewComponentDescriptor = + ConcreteComponentDescriptor; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/scrollview/PullToRefreshViewEventEmitter.cpp b/ReactCommon/fabric/components/scrollview/PullToRefreshViewEventEmitter.cpp new file mode 100644 index 0000000000..43784bde60 --- /dev/null +++ b/ReactCommon/fabric/components/scrollview/PullToRefreshViewEventEmitter.cpp @@ -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. + */ + +#include "PullToRefreshViewEventEmitter.h" + +namespace facebook { +namespace react { + +void PullToRefreshViewEventEmitter::onRefresh() const { + dispatchEvent("refresh"); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/scrollview/PullToRefreshViewEventEmitter.h b/ReactCommon/fabric/components/scrollview/PullToRefreshViewEventEmitter.h new file mode 100644 index 0000000000..77fde7aff7 --- /dev/null +++ b/ReactCommon/fabric/components/scrollview/PullToRefreshViewEventEmitter.h @@ -0,0 +1,25 @@ +/** + * 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. + */ +#pragma once + +#include + +#include +#include + +namespace facebook { +namespace react { + +class PullToRefreshViewEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + void onRefresh() const; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/scrollview/PullToRefreshViewProps.cpp b/ReactCommon/fabric/components/scrollview/PullToRefreshViewProps.cpp new file mode 100644 index 0000000000..38a74f8aa8 --- /dev/null +++ b/ReactCommon/fabric/components/scrollview/PullToRefreshViewProps.cpp @@ -0,0 +1,28 @@ +/** + * 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. + */ + +#include "PullToRefreshViewProps.h" + +#include +#include + +namespace facebook { +namespace react { + +PullToRefreshViewProps::PullToRefreshViewProps( + PullToRefreshViewProps const &sourceProps, + RawProps const &rawProps) + : ViewProps(sourceProps, rawProps), + refreshing( + convertRawProp(rawProps, "refreshing", sourceProps.refreshing)), + tintColor(convertRawProp(rawProps, "tintColor", sourceProps.tintColor)), + title(convertRawProp(rawProps, "title", sourceProps.title)), + titleColor( + convertRawProp(rawProps, "titleColor", sourceProps.titleColor)) {} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/scrollview/PullToRefreshViewProps.h b/ReactCommon/fabric/components/scrollview/PullToRefreshViewProps.h new file mode 100644 index 0000000000..b173ad036a --- /dev/null +++ b/ReactCommon/fabric/components/scrollview/PullToRefreshViewProps.h @@ -0,0 +1,31 @@ +/** + * 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. + */ + +#pragma once + +#include + +namespace facebook { +namespace react { + +class PullToRefreshViewProps final : public ViewProps { + public: + PullToRefreshViewProps() = default; + PullToRefreshViewProps( + PullToRefreshViewProps const &sourceProps, + RawProps const &rawProps); + +#pragma mark - Props + + bool const refreshing{}; + SharedColor const tintColor{}; + std::string const title{}; + SharedColor const titleColor{}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/scrollview/PullToRefreshViewShadowNode.cpp b/ReactCommon/fabric/components/scrollview/PullToRefreshViewShadowNode.cpp new file mode 100644 index 0000000000..e041395c48 --- /dev/null +++ b/ReactCommon/fabric/components/scrollview/PullToRefreshViewShadowNode.cpp @@ -0,0 +1,16 @@ +/** + * 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. + */ + +#include "PullToRefreshViewShadowNode.h" + +namespace facebook { +namespace react { + +const char PullToRefreshViewComponentName[] = "RefreshControl"; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/scrollview/PullToRefreshViewShadowNode.h b/ReactCommon/fabric/components/scrollview/PullToRefreshViewShadowNode.h new file mode 100644 index 0000000000..38a3725841 --- /dev/null +++ b/ReactCommon/fabric/components/scrollview/PullToRefreshViewShadowNode.h @@ -0,0 +1,31 @@ +/** + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +extern const char PullToRefreshViewComponentName[]; + +/* + * `ShadowNode` for component. + */ +class PullToRefreshViewShadowNode final : public ConcreteViewShadowNode< + PullToRefreshViewComponentName, + PullToRefreshViewProps, + PullToRefreshViewEventEmitter> { + public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp index a90c17d87e..da8a830f89 100644 --- a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp +++ b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp @@ -104,8 +104,7 @@ static ComponentName componentNameByReactViewName(ComponentName viewName) { // implementation of core components. if (viewName == "SinglelineTextInputView" || viewName == "MultilineTextInputView" || viewName == "AndroidTextInput" || - viewName == "RefreshControl" || viewName == "SafeAreaView" || - viewName == "ScrollContentView" || + viewName == "SafeAreaView" || viewName == "ScrollContentView" || viewName == "AndroidHorizontalScrollContentView" // Android ) { return "View";