-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[Issue-Resolver] Fix #32244 - RefreshView indicator not displaying on page load #33029
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
base: main
Are you sure you want to change the base?
Conversation
…ing on programmatic refresh (iOS)
|
Hey there @@kubaflo! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
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.
Pull request overview
This PR addresses issue #32244 where the RefreshView refresh indicator would not display on iOS when IsRefreshing is set to true programmatically. The fix uses DispatchQueue.MainQueue.DispatchAsync to defer the BeginRefreshing()/EndRefreshing() calls to the next runloop iteration, giving UIKit time to properly layout the scroll view before initiating the refresh animation.
Key Changes:
- Wrapped the refresh control state changes in an asynchronous dispatch to the main queue
- Added explanatory comment about the iOS UIKit timing requirement
- Maintains the same logical flow for both starting and stopping refresh
| DispatchQueue.MainQueue.DispatchAsync(async () => | ||
| { | ||
| TryOffsetRefresh(this, IsRefreshing); | ||
| _refreshControl.BeginRefreshing(); | ||
| } | ||
| else | ||
| { | ||
| _refreshControl.EndRefreshing(); | ||
| TryOffsetRefresh(this, IsRefreshing); | ||
| } | ||
| if (_isRefreshing) | ||
| { | ||
| TryOffsetRefresh(this, IsRefreshing); | ||
| _refreshControl.BeginRefreshing(); | ||
| } | ||
| else | ||
| { | ||
| _refreshControl.EndRefreshing(); | ||
| TryOffsetRefresh(this, IsRefreshing); | ||
| } | ||
| }); |
Copilot
AI
Dec 6, 2025
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.
The dispatched code block references _isRefreshing which can change between when the dispatch is queued and when it executes, creating a race condition. The value should be captured locally before dispatching.
The pattern should be:
var isRefreshing = _isRefreshing;
DispatchQueue.MainQueue.DispatchAsync(() =>
{
if (isRefreshing)
{
TryOffsetRefresh(this, isRefreshing);
_refreshControl.BeginRefreshing();
}
else
{
_refreshControl.EndRefreshing();
TryOffsetRefresh(this, isRefreshing);
}
});This ensures the dispatched operation uses the value of _isRefreshing from when the property setter was called, preventing inconsistent behavior if IsRefreshing is set multiple times in quick succession.
| if (_isRefreshing != _refreshControl.Refreshing) | ||
| { | ||
| if (_isRefreshing) | ||
| // iOS 26+ quirk: without dispatching to the main queue, the UIRefreshControl |
Copilot
AI
Dec 6, 2025
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.
The comment mentions "iOS 26+" which is incorrect - iOS version numbers have never reached 26. The current latest version is iOS 18. This should be corrected to reference the actual iOS versions where this issue occurs, or simply state "iOS" without a specific version number if it affects all versions.
| // iOS 26+ quirk: without dispatching to the main queue, the UIRefreshControl | |
| // iOS quirk: without dispatching to the main queue, the UIRefreshControl |
| // indicator may not appear. Scheduling BeginRefreshing/EndRefreshing on the | ||
| // next runloop tick ensures the control has been inserted and laid out | ||
| // before we toggle its refreshing state. | ||
| DispatchQueue.MainQueue.DispatchAsync(async () => |
Copilot
AI
Dec 6, 2025
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.
The lambda expression is marked as async but doesn't contain any await statements. This creates an unnecessary async state machine. Since DispatchAsync doesn't return a Task and the lambda body is synchronous, remove the async keyword.
Change:
DispatchQueue.MainQueue.DispatchAsync(async () =>To:
DispatchQueue.MainQueue.DispatchAsync(() =>| DispatchQueue.MainQueue.DispatchAsync(async () => | |
| DispatchQueue.MainQueue.DispatchAsync(() => |
PR: Fix #32244 - RefreshView Indicator Not Displaying on Programmatic Refresh (iOS)
Fixes #32244
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Summary
Fixed an issue on iOS where the RefreshView refresh indicator would not display when
IsRefreshingwas set totrueprogrammatically. The indicator would only appear when the user manually pulled to refresh. This PR ensures the refresh indicator displays correctly in both scenarios.Quick verification:
📋 Click to expand full PR details
Root Cause
When
IsRefreshingis set totrueprogrammatically on iOS, the code was callingUIRefreshControl.BeginRefreshing()synchronously in the same runloop iteration as the scroll view offset change.According to Apple's UIKit documentation and the iOS community,
BeginRefreshing()does not properly animate or display the refresh indicator when:UIScrollView.ContentOffsetThe original implementation:
The issue didn't occur with manual pull-to-refresh because iOS handles the scroll offset and refresh control activation internally through the gesture recognizer, which properly sequences the animation.
Solution
Dispatch the
BeginRefreshing()call to the main queue asynchronously. This ensures UIKit has time to process and render the scroll view contentOffset change before beginning the refresh animation.Files Changed:
src/Core/src/Platform/iOS/MauiRefreshView.cs- Lines 43-50Implementation:
Why this works:
BeginRefreshing()to the next runloop iterationTesting
Before fix:
IsRefreshing = trueprogrammatically → Indicator does NOT appear ❌After fix:
IsRefreshing = trueprogrammatically → Indicator appears ✅User workaround validation:
The reported workaround of adding a 100ms delay before setting
IsRefreshing = trueworked for the same reason - it gave UIKit time to process the layout before callingBeginRefreshing(). This PR eliminates the need for that workaround.Edge Cases Tested
IsRefreshingtrue→false→true quickly doesn't cause crashes or visual glitchesIsRefreshing = truewhen already refreshing (via manual pull) works correctlyIsRefreshing = falseimmediately after setting it totrueworks without issuesPlatforms Tested:
Breaking Changes
None. This is a bug fix that makes programmatic refresh work as expected without changing any public APIs.
Related Issues