From 888d4ac8aeff188e55ef935f01b93cf00872b210 Mon Sep 17 00:00:00 2001 From: EwanThomas Date: Tue, 17 Nov 2015 11:37:24 +0100 Subject: [PATCH 1/6] UIRefreshControl added to scroll view --- Libraries/Components/ScrollView/ScrollView.js | 28 +++++++++++++++++++ React/Views/RCTScrollView.h | 3 ++ React/Views/RCTScrollView.m | 27 ++++++++++++++++++ React/Views/RCTScrollViewManager.m | 18 ++++++++++++ 4 files changed, 76 insertions(+) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 5848527e6443ec..a01c35e0e95b04 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -15,6 +15,7 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var Platform = require('Platform'); var PointPropType = require('PointPropType'); var RCTScrollView = require('NativeModules').UIManager.RCTScrollView; +var RCTScrollViewManager = require('NativeModules').ScrollViewManager; var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var RCTUIManager = require('NativeModules').UIManager; @@ -279,6 +280,21 @@ var ScrollView = React.createClass({ * @platform ios */ zoomScale: PropTypes.number, + + /** + * When defined, displays a UIRefreshControl. + * Invoked with a function to stop refreshing when then UIRefreshControl is animating. + * + * ``` + * (endRefreshing) => { + * endRefreshing(); + * } + * ``` + * + * @platform ios + */ + onRefreshStart: PropTypes.func, + }, mixins: [ScrollResponder.Mixin], @@ -291,6 +307,12 @@ var ScrollView = React.createClass({ this.refs[SCROLLVIEW].setNativeProps(props); }, + endRefreshing: function() { + RCTScrollViewManager.endRefreshing( + React.findNodeHandle(this) + ); + }, + /** * Returns a reference to the underlying scroll responder, which supports * operations like `scrollTo`. All ScrollView-like components should @@ -396,6 +418,12 @@ var ScrollView = React.createClass({ onResponderReject: this.scrollResponderHandleResponderReject, }; + // this is necessary because if we set it on props, even when empty, + // it'll trigger the default pull-to-refresh behaviour on native. + if (this.props.onRefreshStart) { + props.onRefreshStart = () => this.props.onRefreshStart(this.endRefreshing); + } + var ScrollViewClass; if (Platform.OS === 'ios') { ScrollViewClass = RCTScrollView; diff --git a/React/Views/RCTScrollView.h b/React/Views/RCTScrollView.h index d44be6fafae14c..47fb1753b7adab 100644 --- a/React/Views/RCTScrollView.h +++ b/React/Views/RCTScrollView.h @@ -47,6 +47,9 @@ @property (nonatomic, assign) int snapToInterval; @property (nonatomic, copy) NSString *snapToAlignment; @property (nonatomic, copy) NSIndexSet *stickyHeaderIndices; +@property (nonatomic, copy) RCTDirectEventBlock onRefreshStart; + +- (void)endRefreshing; @end diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index e125061f4b786d..2f618db4608499 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -144,6 +144,7 @@ @interface RCTCustomScrollView : UIScrollView @property (nonatomic, copy) NSIndexSet *stickyHeaderIndices; @property (nonatomic, assign) BOOL centerContent; +@property (nonatomic, strong) UIRefreshControl *refreshControl; @end @@ -352,6 +353,12 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event return hitView ?: [super hitTest:point withEvent:event]; } +- (void)setRefreshControl:(UIRefreshControl *)refreshControl +{ + _refreshControl = refreshControl; + [self addSubview:_refreshControl]; +} + @end @implementation RCTScrollView @@ -844,6 +851,26 @@ - (id)valueForUndefinedKey:(NSString *)key return [_scrollView valueForKey:key]; } +- (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart +{ + _onRefreshStart = [onRefreshStart copy]; + UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; + [refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged]; + _scrollView.refreshControl = refreshControl; +} + +- (void)refreshControlValueChanged +{ + if (self.onRefreshStart) { + self.onRefreshStart(nil); + } +} + +- (void)endRefreshing +{ + [_scrollView.refreshControl endRefreshing]; +} + @end @implementation RCTEventDispatcher (RCTScrollView) diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index e90bda75ecf188..fdefe32cc46395 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -65,6 +65,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString) RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint) +RCT_EXPORT_VIEW_PROPERTY(onRefreshStart, RCTDirectEventBlock) - (NSDictionary *)constantsToExport { @@ -114,6 +115,22 @@ - (UIView *)view }]; } +RCT_EXPORT_METHOD(endRefreshing:(nonnull NSNumber *)reactTag) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + + RCTScrollView *view = viewRegistry[reactTag]; + if (!view || ![view isKindOfClass:[RCTScrollView class]]) { + RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag); + return; + } + + [view endRefreshing]; + + }]; +} + + - (NSArray *)customDirectEventTypes { return @[ @@ -127,3 +144,4 @@ - (UIView *)view } @end + From 6aaf3f81038035f7a5265b0adb22d5eb1c152006 Mon Sep 17 00:00:00 2001 From: Peter Minarik Date: Wed, 18 Nov 2015 15:26:09 +0100 Subject: [PATCH 2/6] make sure onRefreshStart exists before calling it --- Libraries/Components/ScrollView/ScrollView.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index a01c35e0e95b04..6135bb449326d7 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -418,11 +418,12 @@ var ScrollView = React.createClass({ onResponderReject: this.scrollResponderHandleResponderReject, }; + var onRefreshStart = this.props.onRefreshStart; // this is necessary because if we set it on props, even when empty, // it'll trigger the default pull-to-refresh behaviour on native. - if (this.props.onRefreshStart) { - props.onRefreshStart = () => this.props.onRefreshStart(this.endRefreshing); - } + props.onRefreshStart = onRefreshStart + ? function() { onRefreshStart && onRefreshStart(this.endRefreshing); } + : null; var ScrollViewClass; if (Platform.OS === 'ios') { From e99fe6e66fd0594fab56766ae128797c1ae2042c Mon Sep 17 00:00:00 2001 From: Peter Minarik Date: Wed, 18 Nov 2015 15:26:42 +0100 Subject: [PATCH 3/6] Fix comment typo --- Libraries/Components/ScrollView/ScrollView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 6135bb449326d7..713d1ee943fcea 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -283,7 +283,7 @@ var ScrollView = React.createClass({ /** * When defined, displays a UIRefreshControl. - * Invoked with a function to stop refreshing when then UIRefreshControl is animating. + * Invoked with a function to stop refreshing when the UIRefreshControl is animating. * * ``` * (endRefreshing) => { From 4da99723cb52727c874c2b6be48af284be07e8ab Mon Sep 17 00:00:00 2001 From: EwanThomas Date: Wed, 18 Nov 2015 17:55:45 +0100 Subject: [PATCH 4/6] defending against a nil onRefreshStart. only creating and setting UIRefreshControl when _scrollView.refreshControl is nil --- React/Views/RCTScrollView.m | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 2f618db4608499..5bb4364d1d674f 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -355,6 +355,11 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event - (void)setRefreshControl:(UIRefreshControl *)refreshControl { + if (!refreshControl) { + [_refreshControl removeFromSuperview]; + _refreshControl = nil; + return; + } _refreshControl = refreshControl; [self addSubview:_refreshControl]; } @@ -853,6 +858,16 @@ - (id)valueForUndefinedKey:(NSString *)key - (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart { + if (!onRefreshStart) { + _onRefreshStart = nil; + _scrollView.refreshControl = nil; + return; + } + + if (_scrollView.refreshControl) { + return; + } + _onRefreshStart = [onRefreshStart copy]; UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; [refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged]; From 6ce32542c9f521ec90753f6bb642d6848e7db870 Mon Sep 17 00:00:00 2001 From: EwanThomas Date: Wed, 18 Nov 2015 18:03:03 +0100 Subject: [PATCH 5/6] binding this. --- Libraries/Components/ScrollView/ScrollView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 713d1ee943fcea..4f394972483d4e 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -422,7 +422,7 @@ var ScrollView = React.createClass({ // this is necessary because if we set it on props, even when empty, // it'll trigger the default pull-to-refresh behaviour on native. props.onRefreshStart = onRefreshStart - ? function() { onRefreshStart && onRefreshStart(this.endRefreshing); } + ? function() { onRefreshStart && onRefreshStart(this.endRefreshing); }.bind(this) : null; var ScrollViewClass; From 37c24e10df96ce85a631c91ff5fd9998b77dfaa6 Mon Sep 17 00:00:00 2001 From: EwanThomas Date: Thu, 19 Nov 2015 11:21:15 +0100 Subject: [PATCH 6/6] updated setting logic for UIRefreshControl + onRefreshStart block --- React/Views/RCTScrollView.m | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 5bb4364d1d674f..2dc6f38868070d 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -355,10 +355,8 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event - (void)setRefreshControl:(UIRefreshControl *)refreshControl { - if (!refreshControl) { + if (_refreshControl) { [_refreshControl removeFromSuperview]; - _refreshControl = nil; - return; } _refreshControl = refreshControl; [self addSubview:_refreshControl]; @@ -863,15 +861,13 @@ - (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart _scrollView.refreshControl = nil; return; } - - if (_scrollView.refreshControl) { - return; - } - _onRefreshStart = [onRefreshStart copy]; - UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; - [refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged]; - _scrollView.refreshControl = refreshControl; + + if (!_scrollView.refreshControl) { + UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; + [refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged]; + _scrollView.refreshControl = refreshControl; + } } - (void)refreshControlValueChanged