Skip to content

Commit

Permalink
UIRefreshControl added to scroll view
Browse files Browse the repository at this point in the history
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#2356
facebook#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#4205

Reviewed By: svcscm

Differential Revision: D2674945

Pulled By: nicklockwood

fb-gh-sync-id: 65113a5db9785df5a95c68323c2cdf19f3b217b1
  • Loading branch information
EwanThomas authored and sunnylqm committed Dec 2, 2015
1 parent db354e9 commit 8a397dc
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 0 deletions.
29 changes: 29 additions & 0 deletions Libraries/Components/ScrollView/ScrollView.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 the UIRefreshControl is animating.
*
* ```
* (endRefreshing) => {
* endRefreshing();
* }
* ```
*
* @platform ios
*/
onRefreshStart: PropTypes.func,

},

mixins: [ScrollResponder.Mixin],
Expand All @@ -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
Expand Down Expand Up @@ -396,6 +418,13 @@ 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.
props.onRefreshStart = onRefreshStart
? function() { onRefreshStart && onRefreshStart(this.endRefreshing); }.bind(this)
: null;

var ScrollViewClass;
if (Platform.OS === 'ios') {
ScrollViewClass = RCTScrollView;
Expand Down
3 changes: 3 additions & 0 deletions React/Views/RCTScrollView.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
38 changes: 38 additions & 0 deletions React/Views/RCTScrollView.m
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ @interface RCTCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>

@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
@property (nonatomic, assign) BOOL centerContent;
@property (nonatomic, strong) UIRefreshControl *refreshControl;

@end

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

- (void)setRefreshControl:(UIRefreshControl *)refreshControl
{
if (_refreshControl) {
[_refreshControl removeFromSuperview];
}
_refreshControl = refreshControl;
[self addSubview:_refreshControl];
}

@end

@implementation RCTScrollView
Expand Down Expand Up @@ -844,6 +854,34 @@ - (id)valueForUndefinedKey:(NSString *)key
return [_scrollView valueForKey:key];
}

- (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart
{
if (!onRefreshStart) {
_onRefreshStart = nil;
_scrollView.refreshControl = nil;
return;
}
_onRefreshStart = [onRefreshStart copy];

if (!_scrollView.refreshControl) {
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)
Expand Down
18 changes: 18 additions & 0 deletions React/Views/RCTScrollViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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<NSString *, id> *)constantsToExport
{
Expand Down Expand Up @@ -114,6 +115,22 @@ - (UIView *)view
}];
}

RCT_EXPORT_METHOD(endRefreshing:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {

RCTScrollView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[RCTScrollView class]]) {
RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
return;
}

[view endRefreshing];

}];
}


- (NSArray<NSString *> *)customDirectEventTypes
{
return @[
Expand All @@ -127,3 +144,4 @@ - (UIView *)view
}

@end

0 comments on commit 8a397dc

Please sign in to comment.