-
Notifications
You must be signed in to change notification settings - Fork 24.4k
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
fix RefreshControl not showing up #14259
Conversation
React/Views/RCTRefreshControl.m
Outdated
@@ -18,12 +18,17 @@ @implementation RCTRefreshControl { | |||
UIColor *_titleColor; | |||
} | |||
|
|||
static unsigned short defaultHeight; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just use an instance var instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I purposely made it static so that the value (which is the same for all instances of refreshControl) is only stored once. Having an instance variable means more memory consumption which in this case is not necessary. Are you sure to change it to per-instance var?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think it is better even if it will be the same value for each instance. RefreshControl is not a component that has many instances so the memory usage is negligible. Also small detail but maybe initialHeight
would be a better name.
React/Views/RCTRefreshControl.m
Outdated
@@ -52,18 +57,9 @@ - (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}; | |||
|
|||
// `beginRefreshing` must be called after the animation is done. This is why it is impossible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure this is not needed anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, it looks even better because the refreshControl is displayed without delay.
Do you mean there is no more animation or the animation is just faster / better now? This code was written on iOS 8 or 9 so it's possible it is not needed anymore and I think it's reasonable to remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
before, the scrollView would move down with animation and after that the refreshControl would show up. I used the refreshControl to indicate fetching in progress. If the fetching was really fast, you'd only see the scrollView move down and then up, without the refreshControl becoming visible.
Now the scrollView moves down with animation and the refreshControl is shown even before the animation is done, so the refreshControl is visible even when the fetching is really fast.
I have only tested this on emulator. Will test it on a device as well and let you know.
React/Views/RCTRefreshControl.m
Outdated
} completion:^(__unused BOOL finished) { | ||
[super beginRefreshing]; | ||
}]; | ||
CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - RCTRefreshControl.defaultHeight}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you also add a small comment that explains why we can't use self.frame.size.height
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when we use self.frame.size.height, it has a issue when we touch and hold to pull at list but just scroll to a part of loading indicator show on screen and then we release it, the height not correct -> beginRefresh will wrong, indicator will not show in nexttime
@vonovak To be honest, this whole solution looks a bit... hacky. Could you please describe how it works and why this is correct solution? |
rename defualtHeight to _initialHeight, fix facebook#10747
@shergin the problem is roughly this: let's consider a ScrollView that has RefreshControl (RC) in it. You control the RC via the 1 . Say you pass 2 . Then you pass 3 . you pass Of course the question is why, and if there is someone who knows where the root cause is, it would be best to fix it there, but I wasn't able to tell and thus implemented this solution, which is more of a workaround. (maybe it's because UIKit manages the size internally and there isn't anything to fix?) |
the PR now fixes also #10747 via the updated |
@vonovak Yes, I understand the problem. I just don't believe in the solution. |
I know we had to add some hacks to RefreshControl on iOS because of some weird UIKit behaviors. When I wrote this UIRefreshControl was more or less supported with UIScrollView and we just added the UIRefreshControl as a child to the UIScrollView and it kind of worked (after a few hacks). With iOS10 there is now an "official" way to use UIRefreshControl with UIScrollView with the |
that would certainly be nice, but if I understand it correctly, that requires iOS 10 while RN supports >= 8.0... While this solution is a hack, it imho isn't all that horrible. Unfortunately I won't be able to put more time into finding a more elegant solution and I'm not even an iOS developer so I'm not familiar with UIKit. If anyone wants to take this over, feel free to do so. |
Thanks for the workaround @vonovak . Are you using this in your production app? Are there any side-effects to your workaround I should be aware of, if I use this in my app too? In particular, curious why you also changed the call to I agree that a proper solution is better for the RN codebase than this hacky workaround. But I also don't have time/experience to fix this properly, and am looking to get a shippable-worthy app again this month after going 0.39->0.45. |
@mikelambert to quote on of my comments on the old diff: (about as for |
I've tried looking into this for a while. After UIRefreshControl's It seems to me like we can't rely on the frame height if it's not going to be static. |
@vonovak I tried to find reviewers for this pull request and wanted to ping them to take another look. However, based on the blame information for the files in this pull request I couldn't find any reviewers. This sometimes happens when the files in the pull request are new or don't exist on master anymore. Is this pull request still relevant? If yes could you please rebase? In case you know who has context on this code feel free to mention them in a comment (one person is fine). Thanks for reading and hope you will continue contributing to the project. |
while this solution is a workaround, it's not so bad imo. closing this since it likely won't land. |
Motivation
There is a bug in the ios implementation of RefreshControl, described in #14056 and #13554. Essentially, it is not possible to programmatically show the refreshControl, because its
self.frame.size.height
starts with a default value of 60, but as the refreshControl hides, the value approaches zero (0.5). The next time we want to show the control, it will not be visible because the scrollView will only move by 0.5 which is not enough for the refreshControl to show up.Test plan
I have built a small demo app to debug the issue. I have tested that the refreshControl works in different situations (start with scrolling, start not scrolling, trigger scroll by pull down gesture repeatedly and also programatically via the prop.)
I have noticed that at the time when
endRefreshing
is called, theself.frame
dimensions are correct. The next timebeginRefreshing
is called the height is completely off though - it is somehow modified in the meantime. That's why I have added another static variable which preserves the value. Let me know what you think of this approach.I have also modified the way the offset is set and the refreshControl gets displayed and it doesn't seem to affect the behavior on ios 9 and 10 emulators. In fact, it looks even better because the refreshControl is displayed without delay.