diff --git a/Examples/UIExplorer/RefreshControlExample.js b/Examples/UIExplorer/RefreshControlExample.js
new file mode 100644
index 00000000000000..026862e356be61
--- /dev/null
+++ b/Examples/UIExplorer/RefreshControlExample.js
@@ -0,0 +1,122 @@
+/**
+* The examples provided by Facebook are for non-commercial testing and
+* evaluation purposes only.
+*
+* Facebook reserves all rights not expressly granted.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*/
+'use strict';
+
+const React = require('react-native');
+const {
+ ScrollView,
+ StyleSheet,
+ RefreshControl,
+ Text,
+ TouchableWithoutFeedback,
+ View,
+} = React;
+
+const styles = StyleSheet.create({
+ row: {
+ borderColor: 'grey',
+ borderWidth: 1,
+ padding: 20,
+ backgroundColor: '#3a5795',
+ margin: 5,
+ },
+ text: {
+ alignSelf: 'center',
+ color: '#fff',
+ },
+ scrollview: {
+ flex: 1,
+ },
+});
+
+const Row = React.createClass({
+ _onClick: function() {
+ this.props.onClick(this.props.data);
+ },
+ render: function() {
+ return (
+
+
+
+ {this.props.data.text + ' (' + this.props.data.clicks + ' clicks)'}
+
+
+
+ );
+ },
+});
+
+const RefreshControlExample = React.createClass({
+ statics: {
+ title: '',
+ description: 'Adds pull-to-refresh support to a scrollview.'
+ },
+
+ getInitialState() {
+ return {
+ isRefreshing: false,
+ loaded: 0,
+ rowData: Array.from(new Array(20)).map(
+ (val, i) => ({text: 'Initial row' + i, clicks: 0})),
+ };
+ },
+
+ _onClick(row) {
+ row.clicks++;
+ this.setState({
+ rowData: this.state.rowData,
+ });
+ },
+
+ render() {
+ const rows = this.state.rowData.map((row, ii) => {
+ return ;
+ });
+ return (
+
+
+ {rows}
+
+ );
+ },
+
+ _onRefresh() {
+ this.setState({isRefreshing: true});
+ setTimeout(() => {
+ // prepend 10 items
+ const rowData = Array.from(new Array(10))
+ .map((val, i) => ({
+ text: 'Loaded row' + (+this.state.loaded + i),
+ clicks: 0,
+ }))
+ .concat(this.state.rowData);
+
+ this.setState({
+ loaded: this.state.loaded + 10,
+ isRefreshing: false,
+ rowData: rowData,
+ });
+ }, 5000);
+ },
+});
+
+module.exports = RefreshControlExample;
diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js
index 7aa70c51633962..81442e0b982e24 100644
--- a/Examples/UIExplorer/UIExplorerList.android.js
+++ b/Examples/UIExplorer/UIExplorerList.android.js
@@ -28,6 +28,7 @@ var COMPONENTS = [
require('./ProgressBarAndroidExample'),
require('./ScrollViewSimpleExample'),
require('./SwitchAndroidExample'),
+ require('./RefreshControlExample'),
require('./PullToRefreshViewAndroidExample.android'),
require('./TextExample.android'),
require('./TextInputExample.android'),
diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js
index c2ba81752099f4..1d96b2d49ce848 100644
--- a/Examples/UIExplorer/UIExplorerList.ios.js
+++ b/Examples/UIExplorer/UIExplorerList.ios.js
@@ -42,6 +42,7 @@ var COMPONENTS = [
require('./NavigatorIOSExample'),
require('./PickerIOSExample'),
require('./ProgressViewIOSExample'),
+ require('./RefreshControlExample'),
require('./ScrollViewExample'),
require('./SegmentedControlIOSExample'),
require('./SliderIOSExample'),
diff --git a/Libraries/Components/RefreshControl/RefreshControl.js b/Libraries/Components/RefreshControl/RefreshControl.js
new file mode 100644
index 00000000000000..9f07c44266f3c8
--- /dev/null
+++ b/Libraries/Components/RefreshControl/RefreshControl.js
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule RefreshControl
+ */
+'use strict';
+
+const React = require('React');
+const Platform = require('Platform');
+
+const requireNativeComponent = require('requireNativeComponent');
+
+if (Platform.OS === 'ios') {
+ var RefreshLayoutConsts = {SIZE: {}};
+} else if (Platform.OS === 'android') {
+ var RefreshLayoutConsts = require('NativeModules').UIManager.AndroidSwipeRefreshLayout.Constants;
+}
+
+/**
+ * This component is used inside a ScrollView to add pull to refresh
+ * functionality. When the ScrollView is at `scrollY: 0`, swiping down
+ * triggers an `onRefresh` event.
+ */
+const RefreshControl = React.createClass({
+ statics: {
+ SIZE: RefreshLayoutConsts.SIZE,
+ },
+
+ propTypes: {
+ /**
+ * Called when the view starts refreshing.
+ */
+ onRefresh: React.PropTypes.func,
+ /**
+ * Whether the view should be indicating an active refresh.
+ */
+ refreshing: React.PropTypes.bool,
+ /**
+ * The color of the refresh indicator.
+ * @platform ios
+ */
+ tintColor: React.PropTypes.string,
+ /**
+ * The title displayed under the refresh indicator.
+ * @platform ios
+ */
+ title: React.PropTypes.string,
+ /**
+ * Whether the pull to refresh functionality is enabled.
+ * @platform android
+ */
+ enabled: React.PropTypes.bool,
+ /**
+ * The colors (at least one) that will be used to draw the refresh indicator.
+ * @platform android
+ */
+ colors: React.PropTypes.arrayOf(React.PropTypes.string),
+ /**
+ * The background color of the refresh indicator.
+ * @platform android
+ */
+ progressBackgroundColor: React.PropTypes.string,
+ /**
+ * Size of the refresh indicator, see PullToRefreshView.SIZE.
+ * @platform android
+ */
+ size: React.PropTypes.oneOf(RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE),
+ },
+
+ render() {
+ if (Platform.OS === 'ios') {
+ return this._renderIOS();
+ } else if (Platform.OS === 'android') {
+ return this._renderAndroid();
+ }
+ },
+
+ _renderIOS() {
+ return (
+
+ );
+ },
+
+ _renderAndroid() {
+ // On Android the ScrollView is wrapped so this component doesn't render
+ // anything and only acts as a way to configure the wrapper view.
+ // ScrollView will wrap itself in a AndroidSwipeRefreshLayout using props
+ // from this.
+ return null;
+ },
+});
+
+if (Platform.OS === 'ios') {
+ var NativeRefreshControl = requireNativeComponent(
+ 'RCTRefreshControl',
+ RefreshControl
+ );
+}
+
+module.exports = RefreshControl;
diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js
index 870044ecaa13ff..ec0035862c0616 100644
--- a/Libraries/Components/ScrollView/ScrollView.js
+++ b/Libraries/Components/ScrollView/ScrollView.js
@@ -31,6 +31,7 @@ var insetsDiffer = require('insetsDiffer');
var invariant = require('invariant');
var pointsDiffer = require('pointsDiffer');
var requireNativeComponent = require('requireNativeComponent');
+var processColor = require('processColor');
var PropTypes = React.PropTypes;
@@ -400,6 +401,21 @@ var ScrollView = React.createClass({
};
}
+ // Extract the RefreshControl from the children if there is one.
+ var refreshControl = null;
+ var children;
+ if (Array.isArray(this.props.children)) {
+ children = this.props.children.filter(c => {
+ if (c && c.type && c.type.displayName === 'RefreshControl') {
+ refreshControl = c;
+ return false;
+ }
+ return true;
+ });
+ } else {
+ children = this.props.children;
+ }
+
var contentContainer =
- {this.props.children}
+ {children}
;
var alwaysBounceHorizontal =
@@ -466,6 +482,32 @@ var ScrollView = React.createClass({
'ScrollViewClass must not be undefined'
);
+ if (refreshControl) {
+ if (Platform.OS === 'ios') {
+ // On iOS the RefreshControl is a child of the ScrollView.
+ return (
+
+ {refreshControl}
+ {contentContainer}
+
+ );
+ } else if (Platform.OS === 'android') {
+ // On Android wrap the ScrollView with a AndroidSwipeRefreshLayout.
+ // Since the ScrollView is wrapped add the style props to the
+ // AndroidSwipeRefreshLayout and use flex: 1 for the ScrollView.
+ var refreshProps = refreshControl.props;
+ return (
+
+
+ {contentContainer}
+
+
+ );
+ }
+ }
return (
{contentContainer}
@@ -519,6 +561,7 @@ if (Platform.OS === 'android') {
'AndroidHorizontalScrollView',
ScrollView
);
+ var AndroidSwipeRefreshLayout = requireNativeComponent('AndroidSwipeRefreshLayout');
} else if (Platform.OS === 'ios') {
var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView);
}
diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js
index 5b33bd36341fcb..b28f255d9c35ff 100644
--- a/Libraries/react-native/react-native.js
+++ b/Libraries/react-native/react-native.js
@@ -35,6 +35,7 @@ var ReactNative = {
get Switch() { return require('Switch'); },
get PullToRefreshViewAndroid() { return require('PullToRefreshViewAndroid'); },
get RecyclerViewBackedScrollView() { return require('RecyclerViewBackedScrollView'); },
+ get RefreshControl() { return require('RefreshControl'); },
get SwitchAndroid() { return require('SwitchAndroid'); },
get SwitchIOS() { return require('SwitchIOS'); },
get TabBarIOS() { return require('TabBarIOS'); },
diff --git a/Libraries/react-native/react-native.js.flow b/Libraries/react-native/react-native.js.flow
index a250e57b0e4b54..7773b27c0281c7 100644
--- a/Libraries/react-native/react-native.js.flow
+++ b/Libraries/react-native/react-native.js.flow
@@ -10,7 +10,7 @@
* and Flow doesn't have a good way to enable getters and setters for
* react-native without forcing all react-native users to also enable getters
* and setters. Until we solve that issue, we can use this .flow file to
- * pretend like react-native doesn't use getters and setters
+ * pretend like react-native doesn't use getters and setters
*
* @flow
*/
@@ -47,6 +47,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
Switch: require('Switch'),
PullToRefreshViewAndroid: require('PullToRefreshViewAndroid'),
RecyclerViewBackedScrollView: require('RecyclerViewBackedScrollView'),
+ RefreshControl: require('RefreshControl'),
SwitchAndroid: require('SwitchAndroid'),
SwitchIOS: require('SwitchIOS'),
TabBarIOS: require('TabBarIOS'),
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index 141bed35aa22dc..a1802c9444e028 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -71,6 +71,8 @@
14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */; };
14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */; };
1BCBD4A71C32FA0B006FC476 /* RCTBundleURLProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BCBD4A61C32FA0B006FC476 /* RCTBundleURLProcessor.m */; };
+ 191E3EBE1C29D9AF00C180A6 /* RCTRefreshControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */; };
+ 191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */; };
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; };
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; };
58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; };
@@ -245,6 +247,10 @@
14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = ""; };
1BCBD4A51C32FA0B006FC476 /* RCTBundleURLProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBundleURLProcessor.h; sourceTree = ""; };
1BCBD4A61C32FA0B006FC476 /* RCTBundleURLProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBundleURLProcessor.m; sourceTree = ""; };
+ 191E3EBC1C29D9AF00C180A6 /* RCTRefreshControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRefreshControlManager.h; sourceTree = ""; };
+ 191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControlManager.m; sourceTree = ""; };
+ 191E3EBF1C29DC3800C180A6 /* RCTRefreshControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRefreshControl.h; sourceTree = ""; };
+ 191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControl.m; sourceTree = ""; };
58114A121AAE854800E7D092 /* RCTPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPicker.h; sourceTree = ""; };
58114A131AAE854800E7D092 /* RCTPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPicker.m; sourceTree = ""; };
58114A141AAE854800E7D092 /* RCTPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPickerManager.h; sourceTree = ""; };
@@ -401,6 +407,10 @@
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */,
13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */,
13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */,
+ 191E3EBF1C29DC3800C180A6 /* RCTRefreshControl.h */,
+ 191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */,
+ 191E3EBC1C29D9AF00C180A6 /* RCTRefreshControlManager.h */,
+ 191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */,
13C325271AA63B6A0048765F /* RCTScrollableProtocol.h */,
13B07FF61A6947C200A75B9A /* RCTScrollView.h */,
13B07FF71A6947C200A75B9A /* RCTScrollView.m */,
@@ -703,6 +713,7 @@
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */,
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */,
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */,
+ 191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */,
13C156051AB1A2840079392D /* RCTWebView.m in Sources */,
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */,
13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */,
@@ -713,6 +724,7 @@
1450FF871BCFF28A00208362 /* RCTProfileTrampoline-arm.S in Sources */,
131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */,
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */,
+ 191E3EBE1C29D9AF00C180A6 /* RCTRefreshControlManager.m in Sources */,
13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */,
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */,
13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */,
diff --git a/React/Views/RCTRefreshControl.h b/React/Views/RCTRefreshControl.h
new file mode 100644
index 00000000000000..7347f546d0f691
--- /dev/null
+++ b/React/Views/RCTRefreshControl.h
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import
+
+#import "RCTComponent.h"
+
+@interface RCTRefreshControl : UIRefreshControl
+
+@property (nonatomic, assign) NSString *title;
+@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
+
+@end
diff --git a/React/Views/RCTRefreshControl.m b/React/Views/RCTRefreshControl.m
new file mode 100644
index 00000000000000..e0f31da3210477
--- /dev/null
+++ b/React/Views/RCTRefreshControl.m
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import "RCTRefreshControl.h"
+
+#import "RCTUtils.h"
+
+@implementation RCTRefreshControl
+
+- (instancetype)init
+{
+ if ((self = [super init])) {
+ [self addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged];
+ }
+ return self;
+}
+
+RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
+
+- (NSString *)title
+{
+ return [self.attributedTitle string];
+}
+
+- (void)setTitle:(NSString *)title
+{
+ self.attributedTitle = [[NSAttributedString alloc] initWithString:title];
+}
+
+- (void)refreshControlValueChanged
+{
+ if (_onRefresh) {
+ _onRefresh(nil);
+ }
+}
+
+@end
diff --git a/React/Views/RCTRefreshControlManager.h b/React/Views/RCTRefreshControlManager.h
new file mode 100644
index 00000000000000..8d1c3f961668e0
--- /dev/null
+++ b/React/Views/RCTRefreshControlManager.h
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import "RCTViewManager.h"
+
+@interface RCTRefreshControlManager : RCTViewManager
+
+@end
diff --git a/React/Views/RCTRefreshControlManager.m b/React/Views/RCTRefreshControlManager.m
new file mode 100644
index 00000000000000..b034a7ba4a64b5
--- /dev/null
+++ b/React/Views/RCTRefreshControlManager.m
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import "RCTRefreshControlManager.h"
+
+#import "RCTRefreshControl.h"
+
+@implementation RCTRefreshControlManager
+
+RCT_EXPORT_MODULE()
+
+- (UIView *)view
+{
+ return [RCTRefreshControl new];
+}
+
+RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(title, NSString)
+RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock)
+RCT_CUSTOM_VIEW_PROPERTY(refreshing, BOOL, RCTRefreshControl)
+{
+ if (json ? [RCTConvert BOOL:json] : defaultView.refreshing) {
+ [view beginRefreshing];
+ } else {
+ [view endRefreshing];
+ }
+}
+
+@end
diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m
index 5ae7fb05a6b5f1..e5e2ffceddc70c 100644
--- a/React/Views/RCTScrollView.m
+++ b/React/Views/RCTScrollView.m
@@ -14,6 +14,7 @@
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
+#import "RCTRefreshControl.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "UIView+Private.h"
@@ -410,16 +411,24 @@ - (void)setRemoveClippedSubviews:(__unused BOOL)removeClippedSubviews
- (void)insertReactSubview:(UIView *)view atIndex:(__unused NSInteger)atIndex
{
- RCTAssert(_contentView == nil, @"RCTScrollView may only contain a single subview");
- _contentView = view;
- [_scrollView addSubview:view];
+ if ([view isKindOfClass:[RCTRefreshControl class]]) {
+ _scrollView.refreshControl = (RCTRefreshControl*)view;
+ } else {
+ RCTAssert(_contentView == nil, @"RCTScrollView may only contain a single subview");
+ _contentView = view;
+ [_scrollView addSubview:view];
+ }
}
- (void)removeReactSubview:(UIView *)subview
{
- RCTAssert(_contentView == subview, @"Attempted to remove non-existent subview");
- _contentView = nil;
- [subview removeFromSuperview];
+ if ([subview isKindOfClass:[RCTRefreshControl class]]) {
+ _scrollView.refreshControl = nil;
+ } else {
+ RCTAssert(_contentView == subview, @"Attempted to remove non-existent subview");
+ _contentView = nil;
+ [subview removeFromSuperview];
+ }
}
- (NSArray *)reactSubviews