Skip to content

Commit 1b0fb9b

Browse files
yogevbdfacebook-github-bot
authored andcommitted
iOS: Fix refreshControl layouting (#28236)
Summary: In `react-native-navigation` we allow the usage of native iOS navigationBar **largeTitle** which cause the title to "jump" when pulling to refresh. We found that the layout calculations of the refreshControl element mess up the system behaviour. ## Changelog [iOS] [Fixed] - Fix refreshControl messes up navigationBar largeTitles Pull Request resolved: #28236 Test Plan: ### Before the fix: ![before](https://user-images.githubusercontent.com/10794586/75991307-f7c7ec00-5efe-11ea-8cd9-ab8c3fbe1dc1.gif) ### And after: ![after](https://user-images.githubusercontent.com/10794586/75990618-d9152580-5efd-11ea-8c72-5deb6d83a840.gif) ### How it looks like with react-native init app after the fix: ![ezgif com-video-to-gif (4)](https://user-images.githubusercontent.com/10794586/77253369-54970680-6c62-11ea-9ad6-3265e23044e6.gif) Reviewed By: sammy-SC Differential Revision: D22782680 Pulled By: PeteTheHeat fbshipit-source-id: f86ccd0a6ad492312029a69b392cd525450fe594
1 parent b179c4b commit 1b0fb9b

File tree

4 files changed

+41
-27
lines changed

4 files changed

+41
-27
lines changed

React/Views/RefreshControl/RCTRefreshControl.h

+1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414

1515
@property (nonatomic, copy) NSString *title;
1616
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
17+
@property (nonatomic, weak) UIScrollView *scrollView;
1718

1819
@end

React/Views/RefreshControl/RCTRefreshControl.m

+27-25
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,6 @@ - (void)layoutSubviews
4141
{
4242
[super layoutSubviews];
4343

44-
// Fix for bug #7976
45-
// TODO: Remove when updating to use iOS 10 refreshControl UIScrollView prop.
46-
if (self.backgroundColor == nil) {
47-
self.backgroundColor = [UIColor clearColor];
48-
}
49-
5044
// If the control is refreshing when mounted we need to call
5145
// beginRefreshing in layoutSubview or it doesn't work.
5246
if (_currentRefreshingState && _isInitialRender) {
@@ -59,34 +53,42 @@ - (void)beginRefreshingProgrammatically
5953
{
6054
UInt64 beginRefreshingTimestamp = _currentRefreshingStateTimestamp;
6155
_refreshingProgrammatically = YES;
62-
// When using begin refreshing we need to adjust the ScrollView content offset manually.
63-
UIScrollView *scrollView = (UIScrollView *)self.superview;
56+
6457
// Fix for bug #24855
6558
[self sizeToFit];
66-
CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height};
67-
68-
// `beginRefreshing` must be called after the animation is done. This is why it is impossible
69-
// to use `setContentOffset` with `animated:YES`.
70-
[UIView animateWithDuration:0.25
71-
delay:0
72-
options:UIViewAnimationOptionBeginFromCurrentState
73-
animations:^(void) {
74-
[scrollView setContentOffset:offset];
75-
}
76-
completion:^(__unused BOOL finished) {
77-
if (beginRefreshingTimestamp == self->_currentRefreshingStateTimestamp) {
78-
[super beginRefreshing];
79-
[self setCurrentRefreshingState:super.refreshing];
59+
60+
if (self.scrollView) {
61+
// When using begin refreshing we need to adjust the ScrollView content offset manually.
62+
UIScrollView *scrollView = (UIScrollView *)self.scrollView;
63+
64+
CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height};
65+
66+
// `beginRefreshing` must be called after the animation is done. This is why it is impossible
67+
// to use `setContentOffset` with `animated:YES`.
68+
[UIView animateWithDuration:0.25
69+
delay:0
70+
options:UIViewAnimationOptionBeginFromCurrentState
71+
animations:^(void) {
72+
[scrollView setContentOffset:offset];
8073
}
81-
}];
74+
completion:^(__unused BOOL finished) {
75+
if (beginRefreshingTimestamp == self->_currentRefreshingStateTimestamp) {
76+
[super beginRefreshing];
77+
[self setCurrentRefreshingState:super.refreshing];
78+
}
79+
}];
80+
} else if (beginRefreshingTimestamp == self->_currentRefreshingStateTimestamp) {
81+
[super beginRefreshing];
82+
[self setCurrentRefreshingState:super.refreshing];
83+
}
8284
}
8385

8486
- (void)endRefreshingProgrammatically
8587
{
8688
// The contentOffset of the scrollview MUST be greater than the contentInset before calling
8789
// endRefreshing otherwise the next pull to refresh will not work properly.
88-
UIScrollView *scrollView = (UIScrollView *)self.superview;
89-
if (_refreshingProgrammatically && scrollView.contentOffset.y < -scrollView.contentInset.top) {
90+
UIScrollView *scrollView = self.scrollView;
91+
if (scrollView && _refreshingProgrammatically && scrollView.contentOffset.y < -scrollView.contentInset.top) {
9092
UInt64 endRefreshingTimestamp = _currentRefreshingStateTimestamp;
9193
CGPoint offset = {scrollView.contentOffset.x, -scrollView.contentInset.top};
9294
[UIView animateWithDuration:0.25

React/Views/ScrollView/RCTScrollView.m

+10-2
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,15 @@ - (void)setCustomRefreshControl:(UIView<RCTCustomRefreshContolProtocol> *)refres
230230
[_customRefreshControl removeFromSuperview];
231231
}
232232
_customRefreshControl = refreshControl;
233-
[self addSubview:_customRefreshControl];
233+
// We have to set this because we can't always guarantee the
234+
// `RCTCustomRefreshContolProtocol`'s superview will always be of class
235+
// `UIScrollView` like we were previously
236+
_customRefreshControl.scrollView = self;
237+
if ([refreshControl isKindOfClass:UIRefreshControl.class]) {
238+
self.refreshControl = (UIRefreshControl *)refreshControl;
239+
} else {
240+
[self addSubview:_customRefreshControl];
241+
}
234242
}
235243

236244
- (void)setPinchGestureEnabled:(BOOL)pinchGestureEnabled
@@ -421,7 +429,7 @@ - (void)layoutSubviews
421429
#if !TARGET_OS_TV
422430
// Adjust the refresh control frame if the scrollview layout changes.
423431
UIView<RCTCustomRefreshContolProtocol> *refreshControl = _scrollView.customRefreshControl;
424-
if (refreshControl && refreshControl.isRefreshing) {
432+
if (refreshControl && refreshControl.isRefreshing && ![refreshControl isKindOfClass:UIRefreshControl.class]) {
425433
refreshControl.frame =
426434
(CGRect){_scrollView.contentOffset, {_scrollView.frame.size.width, refreshControl.frame.size.height}};
427435
}

React/Views/ScrollView/RCTScrollableProtocol.h

+3
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,7 @@
3838
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
3939
@property (nonatomic, readonly, getter=isRefreshing) BOOL refreshing;
4040

41+
@optional
42+
@property (nonatomic, weak) UIScrollView *scrollView;
43+
4144
@end

0 commit comments

Comments
 (0)