diff --git a/React/Views/RefreshControl/RCTRefreshControl.h b/React/Views/RefreshControl/RCTRefreshControl.h index 817576523835f5..c17b226e069fdc 100644 --- a/React/Views/RefreshControl/RCTRefreshControl.h +++ b/React/Views/RefreshControl/RCTRefreshControl.h @@ -14,5 +14,6 @@ @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) RCTDirectEventBlock onRefresh; +@property (nonatomic, weak) UIScrollView *scrollView; @end diff --git a/React/Views/RefreshControl/RCTRefreshControl.m b/React/Views/RefreshControl/RCTRefreshControl.m index 428a5e7fa406b9..1022b7896d8867 100644 --- a/React/Views/RefreshControl/RCTRefreshControl.m +++ b/React/Views/RefreshControl/RCTRefreshControl.m @@ -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) { @@ -59,34 +53,42 @@ - (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 diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index acef7bc88538b6..80a7d09714c51c 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -230,7 +230,15 @@ - (void)setCustomRefreshControl:(UIView *)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 @@ -421,7 +429,7 @@ - (void)layoutSubviews #if !TARGET_OS_TV // Adjust the refresh control frame if the scrollview layout changes. UIView *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}}; } diff --git a/React/Views/ScrollView/RCTScrollableProtocol.h b/React/Views/ScrollView/RCTScrollableProtocol.h index 7800e796cc8a6e..ec6772e8f31421 100644 --- a/React/Views/ScrollView/RCTScrollableProtocol.h +++ b/React/Views/ScrollView/RCTScrollableProtocol.h @@ -38,4 +38,7 @@ @property (nonatomic, copy) RCTDirectEventBlock onRefresh; @property (nonatomic, readonly, getter=isRefreshing) BOOL refreshing; +@optional +@property (nonatomic, weak) UIScrollView *scrollView; + @end