Allows RefreshControl to be mounted with refreshing = true

Summary:
RefreshControl did not start refreshing when refreshing was set to true initially. It also did not start refreshing on iOS when setting the prop from false to true without doing a pull to refresh gesture.

This was a pain in the ass to make work on iOS because UIRefreshControl seems super sensitive to when beginRefreshing can be called, for the initial render I need to call it in layoutSubviews. I also have to manually adjust the scrollview content offset when calling beginRefreshing. The code is a bit hacky but it was the only solution I found that was actually working.

Fixes #5716
Closes https://github.com/facebook/react-native/pull/5745

Reviewed By: svcscm

Differential Revision: D2910716

Pulled By: nicklockwood

fb-gh-sync-id: d60e73bcfe8d86bb01249ba5f17e6a23c5a5aff6
This commit is contained in:
Janic Duplessis 2016-02-07 13:39:17 -08:00 committed by facebook-github-bot-5
parent 7b22606f49
commit 3e1f1ea7bb
2 changed files with 59 additions and 5 deletions

View File

@ -11,18 +11,59 @@
#import "RCTUtils.h" #import "RCTUtils.h"
@implementation RCTRefreshControl @implementation RCTRefreshControl {
BOOL _initialRefreshingState;
BOOL _isInitialRender;
}
- (instancetype)init - (instancetype)init
{ {
if ((self = [super init])) { if ((self = [super init])) {
[self addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged]; [self addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged];
_isInitialRender = true;
} }
return self; return self;
} }
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)layoutSubviews
{
[super layoutSubviews];
// If the control is refreshing when mounted we need to call
// beginRefreshing in layoutSubview or it doesn't work.
if (_isInitialRender && _initialRefreshingState) {
[self beginRefreshing];
}
_isInitialRender = false;
}
- (void)beginRefreshing
{
// When using begin refreshing we need to adjust the ScrollView content offset manually.
UIScrollView *scrollView = (UIScrollView *)self.superview;
CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height};
// Don't animate when the prop is set initialy.
if (_isInitialRender) {
// Must use `[scrollView setContentOffset:offset animated:NO]` instead of just setting
// `scrollview.contentOffset` or it doesn't work, don't ask me why!
[scrollView setContentOffset:offset animated:NO];
[super beginRefreshing];
} else {
// `beginRefreshing` must be called after the animation is done. This is why it is impossible
// to use `setContentOffset` with `animated:YES`.
[UIView animateWithDuration:0.25
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^(void) {
[scrollView setContentOffset:offset];
} completion:^(__unused BOOL finished) {
[super beginRefreshing];
}];
}
}
- (NSString *)title - (NSString *)title
{ {
return self.attributedTitle.string; return self.attributedTitle.string;
@ -35,9 +76,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)setRefreshing:(BOOL)refreshing - (void)setRefreshing:(BOOL)refreshing
{ {
if (super.refreshing != refreshing) { if (self.refreshing != refreshing) {
if (refreshing) { if (refreshing) {
[self beginRefreshing]; // If it is the initial render, beginRefreshing will get called
// in layoutSubviews.
if (_isInitialRender) {
_initialRefreshingState = refreshing;
} else {
[self beginRefreshing];
}
} else { } else {
[self endRefreshing]; [self endRefreshing];
} }

View File

@ -71,8 +71,15 @@ public class SwipeRefreshLayoutManager extends ViewGroupManager<ReactSwipeRefres
} }
@ReactProp(name = "refreshing") @ReactProp(name = "refreshing")
public void setRefreshing(ReactSwipeRefreshLayout view, boolean refreshing) { public void setRefreshing(final ReactSwipeRefreshLayout view, final boolean refreshing) {
view.setRefreshing(refreshing); // Use `post` otherwise the control won't start refreshing if refreshing is true when
// the component gets mounted.
view.post(new Runnable() {
@Override
public void run() {
view.setRefreshing(refreshing);
}
});
} }
@Override @Override