iOS: Loading Overlay Sometimes Fails to Dismiss After Async Operation

Connor Patchett 0 Reputation points
2025-12-04T15:35:09.68+00:00

I have a method in my .NET MAUI app that triggers a loading overlay with a spinning sync icon whenever it runs. When execution reaches the finally block, the overlay and icon should be removed. This works consistently on the Windows emulator, but on iOS I occasionally run into an issue where the loading state becomes “stuck.” The overlay remains visible and the sync icon never disappears, and the only way to clear it is to fully delete the app rather than simply closing it. I’ve included code snippets below for reference.

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                await Sync();
                if (noticeId.HasValue)
                {
                    var notice = noticePrioritiesWithNotices.SelectMany(x => 			x.Notices).FirstOrDefault(n => n.IDAlert == noticeId);
                    if (notice != null)
                    {
                        await UpdateNoticeContentView(notice);
                    }
                }
            }
        }

	private async Task Sync()
    {
        try
        {
            showLoadingRipple = true;
            syncIconClass = "spin-image";
            selectedNotice = null;

            await InvokeAsync(StateHasChanged); 
            await ListUserAlerts();
            await DriversCompanionLogin();
            await PopupNotices();
        }
        catch (Exception ex)
        {
			//Log Ex
        }
        finally
        {
            try
            {
                syncIconClass = string.Empty
                showLoadingRipple = false;
                await InvokeAsync(StateHasChanged);
            }
            catch (Exception ex)
            {		
				//Log Ex
            }
        }
    }

Razor:
@if (showLoadingRipple)
{
    <LoadingRipple></LoadingRipple>
}
Developer technologies | .NET | .NET MAUI
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Michael Le (WICLOUD CORPORATION) 5,930 Reputation points Microsoft External Staff Moderator
    2025-12-05T07:58:24.4433333+00:00

    Hello @Connor Patchett ,

    On iOS, app suspension and the main RunLoop’s scheduling can delay or stall UI updates; if a backgrounded network await never completes, InvokeAsync(StateHasChanged) waits and the overlay stays up.

    I recommend adding timeouts/cancellation to ensure the UI clears.

    private async Task Sync()
    {
        using var cts = new CancellationTokenSource(TimeSpan. FromSeconds(10));
        try
        {
            showLoadingRipple = true;
            syncIconClass = "spin-image";
            selectedNotice = null;
            await InvokeAsync(StateHasChanged);
    
            // Pass cancellation token to all async operations
            await ListUserAlerts(cts.Token);
            await DriversCompanionLogin(cts.Token);
            await PopupNotices(cts.Token);
        }
        catch (OperationCanceledException)
        {
            // Handle timeout gracefully
            await LogAsync("Sync operation timed out");
        }
        catch (Exception ex)
        {
            await LogAsync($"Sync error: {ex.Message}");
        }
        finally
        {
            // Ensure UI updates on main thread for iOS
            await MainThread.InvokeAsync(() =>
            {
                showLoadingRipple = false;
                syncIconClass = string.Empty;
                StateHasChanged();
            });
        }
    }
    

    For more robust iOS support, you might want to explore using background URL sessions for network operations that need to complete even when the app is suspended.

    While this is not a Microsoft website, it is the official site of Apple.

    1 person found this answer helpful.
    0 comments No comments

  2. Q&A Assist
    2025-12-04T15:35:27.5533333+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    It seems like you're encountering an issue with the loading overlay not dismissing properly on iOS after an asynchronous operation. This can sometimes happen due to the way the UI thread is managed in iOS, especially if there are delays or blocking calls that prevent the UI from updating correctly.

    Here are a few suggestions to troubleshoot and potentially resolve the issue:

    1. Ensure UI Updates on the Main Thread: Make sure that all UI updates are being made on the main thread. While you are using InvokeAsync(StateHasChanged), ensure that this is called appropriately and that there are no other blocking calls that could prevent the UI from updating.
    2. Check for Exceptions: In your catch blocks, ensure that you are logging any exceptions that might occur. If an exception is thrown before the finally block is reached, it could prevent the loading overlay from being dismissed.
    3. Use Task.Delay: Sometimes adding a small delay before dismissing the loading overlay can help. This allows the UI to catch up with the changes. For example:
         await Task.Delay(100); // Add a small delay
         syncIconClass = string.Empty;
         showLoadingRipple = false;
         await InvokeAsync(StateHasChanged);
      
    4. Test on Physical Device: If you are testing on an emulator, try testing on a physical iOS device as well. Emulators can sometimes behave differently than actual devices.
    5. Review the Loading Overlay Implementation: Ensure that the LoadingRipple component is implemented correctly and that it properly handles visibility changes. Sometimes, issues can arise from how components are rendered or updated.

    If the problem persists, consider simplifying the Sync method to isolate the issue or creating a minimal reproducible example to further diagnose the problem.


    References:

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.