Skip to content

Commit 2faf863

Browse files
EwanThomasfacebook-github-bot-4
authored andcommitted
UIRefreshControl added to scroll view
Summary: **What:** adds `onRefreshStart` property to `ScrollView.js` for displaying and activating pull to refresh. **Why:** Javascript implementations seemed a little flakey and inconsistent. As you can see in the issues below: facebook/react-native#2356 facebook/react-native#745 So this is an attempt a completely native implementation. What do you think? ![Image of dog](http://i.imgur.com/HcTQnzJ.gif) Closes facebook/react-native#4205 Reviewed By: svcscm Differential Revision: D2674945 Pulled By: nicklockwood fb-gh-sync-id: 65113a5db9785df5a95c68323c2cdf19f3b217b1
1 parent 5950f8c commit 2faf863

4 files changed

Lines changed: 88 additions & 0 deletions

File tree

Libraries/Components/ScrollView/ScrollView.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType');
1515
var Platform = require('Platform');
1616
var PointPropType = require('PointPropType');
1717
var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
18+
var RCTScrollViewManager = require('NativeModules').ScrollViewManager;
1819
var React = require('React');
1920
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
2021
var RCTUIManager = require('NativeModules').UIManager;
@@ -279,6 +280,21 @@ var ScrollView = React.createClass({
279280
* @platform ios
280281
*/
281282
zoomScale: PropTypes.number,
283+
284+
/**
285+
* When defined, displays a UIRefreshControl.
286+
* Invoked with a function to stop refreshing when the UIRefreshControl is animating.
287+
*
288+
* ```
289+
* (endRefreshing) => {
290+
* endRefreshing();
291+
* }
292+
* ```
293+
*
294+
* @platform ios
295+
*/
296+
onRefreshStart: PropTypes.func,
297+
282298
},
283299

284300
mixins: [ScrollResponder.Mixin],
@@ -291,6 +307,12 @@ var ScrollView = React.createClass({
291307
this.refs[SCROLLVIEW].setNativeProps(props);
292308
},
293309

310+
endRefreshing: function() {
311+
RCTScrollViewManager.endRefreshing(
312+
React.findNodeHandle(this)
313+
);
314+
},
315+
294316
/**
295317
* Returns a reference to the underlying scroll responder, which supports
296318
* operations like `scrollTo`. All ScrollView-like components should
@@ -396,6 +418,13 @@ var ScrollView = React.createClass({
396418
onResponderReject: this.scrollResponderHandleResponderReject,
397419
};
398420

421+
var onRefreshStart = this.props.onRefreshStart;
422+
// this is necessary because if we set it on props, even when empty,
423+
// it'll trigger the default pull-to-refresh behaviour on native.
424+
props.onRefreshStart = onRefreshStart
425+
? function() { onRefreshStart && onRefreshStart(this.endRefreshing); }.bind(this)
426+
: null;
427+
399428
var ScrollViewClass;
400429
if (Platform.OS === 'ios') {
401430
ScrollViewClass = RCTScrollView;

React/Views/RCTScrollView.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
@property (nonatomic, assign) int snapToInterval;
4848
@property (nonatomic, copy) NSString *snapToAlignment;
4949
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
50+
@property (nonatomic, copy) RCTDirectEventBlock onRefreshStart;
51+
52+
- (void)endRefreshing;
5053

5154
@end
5255

React/Views/RCTScrollView.m

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ @interface RCTCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>
144144

145145
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
146146
@property (nonatomic, assign) BOOL centerContent;
147+
@property (nonatomic, strong) UIRefreshControl *refreshControl;
147148

148149
@end
149150

@@ -352,6 +353,15 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
352353
return hitView ?: [super hitTest:point withEvent:event];
353354
}
354355

356+
- (void)setRefreshControl:(UIRefreshControl *)refreshControl
357+
{
358+
if (_refreshControl) {
359+
[_refreshControl removeFromSuperview];
360+
}
361+
_refreshControl = refreshControl;
362+
[self addSubview:_refreshControl];
363+
}
364+
355365
@end
356366

357367
@implementation RCTScrollView
@@ -844,6 +854,34 @@ - (id)valueForUndefinedKey:(NSString *)key
844854
return [_scrollView valueForKey:key];
845855
}
846856

857+
- (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart
858+
{
859+
if (!onRefreshStart) {
860+
_onRefreshStart = nil;
861+
_scrollView.refreshControl = nil;
862+
return;
863+
}
864+
_onRefreshStart = [onRefreshStart copy];
865+
866+
if (!_scrollView.refreshControl) {
867+
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
868+
[refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged];
869+
_scrollView.refreshControl = refreshControl;
870+
}
871+
}
872+
873+
- (void)refreshControlValueChanged
874+
{
875+
if (self.onRefreshStart) {
876+
self.onRefreshStart(nil);
877+
}
878+
}
879+
880+
- (void)endRefreshing
881+
{
882+
[_scrollView.refreshControl endRefreshing];
883+
}
884+
847885
@end
848886

849887
@implementation RCTEventDispatcher (RCTScrollView)

React/Views/RCTScrollViewManager.m

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ - (UIView *)view
6565
RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
6666
RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString)
6767
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
68+
RCT_EXPORT_VIEW_PROPERTY(onRefreshStart, RCTDirectEventBlock)
6869

6970
- (NSDictionary<NSString *, id> *)constantsToExport
7071
{
@@ -114,6 +115,22 @@ - (UIView *)view
114115
}];
115116
}
116117

118+
RCT_EXPORT_METHOD(endRefreshing:(nonnull NSNumber *)reactTag)
119+
{
120+
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {
121+
122+
RCTScrollView *view = viewRegistry[reactTag];
123+
if (!view || ![view isKindOfClass:[RCTScrollView class]]) {
124+
RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
125+
return;
126+
}
127+
128+
[view endRefreshing];
129+
130+
}];
131+
}
132+
133+
117134
- (NSArray<NSString *> *)customDirectEventTypes
118135
{
119136
return @[
@@ -127,3 +144,4 @@ - (UIView *)view
127144
}
128145

129146
@end
147+

0 commit comments

Comments
 (0)