Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS: Fix refreshControl layouting #28236

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions React/Views/RefreshControl/RCTRefreshControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
@property (nonatomic, weak) UIScrollView *scrollView;

@end
55 changes: 29 additions & 26 deletions React/Views/RefreshControl/RCTRefreshControl.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,6 @@ - (void)layoutSubviews
{
[super layoutSubviews];

// Fix for bug #7976
// TODO: Remove when updating to use iOS 10 refreshControl UIScrollView prop.
if (self.backgroundColor == nil) {
self.backgroundColor = [UIColor clearColor];
}

// If the control is refreshing when mounted we need to call
// beginRefreshing in layoutSubview or it doesn't work.
if (_currentRefreshingState && _isInitialRender) {
Expand All @@ -59,34 +53,43 @@ - (void)beginRefreshingProgrammatically
{
UInt64 beginRefreshingTimestamp = _currentRefreshingStateTimestamp;
_refreshingProgrammatically = YES;
// When using begin refreshing we need to adjust the ScrollView content offset manually.
UIScrollView *scrollView = (UIScrollView *)self.superview;

// Fix for bug #24855
[self sizeToFit];
CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height};

// `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) {
if (beginRefreshingTimestamp == self->_currentRefreshingStateTimestamp) {
[super beginRefreshing];
[self setCurrentRefreshingState:super.refreshing];
}
}];

if (self.scrollView) {

// When using begin refreshing we need to adjust the ScrollView content offset manually.
UIScrollView *scrollView = (UIScrollView *)self.scrollView;

CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height};

// `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) {
if (beginRefreshingTimestamp == self->_currentRefreshingStateTimestamp) {
[super beginRefreshing];
[self setCurrentRefreshingState:super.refreshing];
}
}];
} else if (beginRefreshingTimestamp == self->_currentRefreshingStateTimestamp) {
[super beginRefreshing];
[self setCurrentRefreshingState:super.refreshing];
}
}

- (void)endRefreshingProgrammatically
{
// The contentOffset of the scrollview MUST be greater than the contentInset before calling
// endRefreshing otherwise the next pull to refresh will not work properly.
UIScrollView *scrollView = (UIScrollView *)self.superview;
if (_refreshingProgrammatically && scrollView.contentOffset.y < -scrollView.contentInset.top) {
UIScrollView *scrollView = self.scrollView;
if (scrollView && _refreshingProgrammatically && scrollView.contentOffset.y < -scrollView.contentInset.top) {
UInt64 endRefreshingTimestamp = _currentRefreshingStateTimestamp;
CGPoint offset = {scrollView.contentOffset.x, -scrollView.contentInset.top};
[UIView animateWithDuration:0.25
Expand Down
12 changes: 10 additions & 2 deletions React/Views/ScrollView/RCTScrollView.m
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,15 @@ - (void)setCustomRefreshControl:(UIView<RCTCustomRefreshContolProtocol> *)refres
[_customRefreshControl removeFromSuperview];
}
_customRefreshControl = refreshControl;
[self addSubview:_customRefreshControl];
// We have to set this because we can't always guarantee the
// `RCTCustomRefreshContolProtocol`'s superview will always be of class
// `UIScrollView` like we were previously
_customRefreshControl.scrollView = self;
if ([refreshControl isKindOfClass:UIRefreshControl.class]) {
self.refreshControl = (UIRefreshControl *)refreshControl;
} else {
[self addSubview:_customRefreshControl];
}
}

- (void)setPinchGestureEnabled:(BOOL)pinchGestureEnabled
Expand Down Expand Up @@ -421,7 +429,7 @@ - (void)layoutSubviews
#if !TARGET_OS_TV
// Adjust the refresh control frame if the scrollview layout changes.
UIView<RCTCustomRefreshContolProtocol> *refreshControl = _scrollView.customRefreshControl;
if (refreshControl && refreshControl.isRefreshing) {
if (refreshControl && refreshControl.isRefreshing && ![refreshControl isKindOfClass:UIRefreshControl.class]) {
refreshControl.frame =
(CGRect){_scrollView.contentOffset, {_scrollView.frame.size.width, refreshControl.frame.size.height}};
}
Expand Down
1 change: 1 addition & 0 deletions React/Views/ScrollView/RCTScrollableProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@

@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
@property (nonatomic, readonly, getter=isRefreshing) BOOL refreshing;
@property (nonatomic, weak) UIScrollView *scrollView;

@end