Skip to content

Conversation

@bhavanesh2001
Copy link
Contributor

@bhavanesh2001 bhavanesh2001 commented Apr 14, 2025

Description of Change

On iOS, the ActivityIndicator becomes visible even when IsVisible = false and IsRunning = true, effectively ignoring the IsVisible property.

Root Cause

Visibility is not being correctly mapped in iOS.

By default, IsVisible is true for all elements, so we must explicitly manage visibility based on the IsRunning value — especially since it's expected that the ActivityIndicator is hidden by default.

The current implementation seems to work because IsRunning defaults to false, which calls StopAnimating() and hides the native control. However, if HidesWhenStopped is set to false, the control remains visible without animating — which is incorrect.

To fix this, we explicitly update the visibility inside the IsRunning mapper, just as it's done on Android.

Before Fix After Fix
before_fix.mov
after_fix.mov

Issues Fixed

Closes #28968

@bhavanesh2001 bhavanesh2001 requested a review from a team as a code owner April 14, 2025 19:06
@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Apr 14, 2025
@bhavanesh2001
Copy link
Contributor Author

  <VerticalStackLayout VerticalOptions="Center" HorizontalOptions="Center"> 
                <ActivityIndicator HorizontalOptions="Center" x:Name="busy"> 
                    <ActivityIndicator.Triggers>
                        <DataTrigger TargetType="ActivityIndicator" 
                            Binding="{Binding Source={x:Reference isRunningSwitch}, Path=IsToggled}" 
                            Value="True">
                             <Setter Property="IsRunning" Value="True" />
                        </DataTrigger>
                        <DataTrigger TargetType="ActivityIndicator" 
                            Binding="{Binding Source={x:Reference isVisibleSwitch}, Path=IsToggled}" 
                            Value="True"> 
                            <Setter Property="IsVisible" Value="True" /> 
                        </DataTrigger> 
                        <DataTrigger TargetType="ActivityIndicator" 
                            Binding="{Binding Source={x:Reference isRunningSwitch}, Path=IsToggled}" 
                            Value="False"> 
                            <Setter Property="IsRunning" Value="False" /> 
                        </DataTrigger> 
                        <DataTrigger TargetType="ActivityIndicator" 
                            Binding="{Binding Source={x:Reference isVisibleSwitch}, Path=IsToggled}" 
                            Value="False"> <Setter Property="IsVisible" Value="False" /> 
                        </DataTrigger> 
                    </ActivityIndicator.Triggers> 
                </ActivityIndicator> 
                <Switch x:Name="isRunningSwitch" HorizontalOptions="Center" /> 
                <Label Text="Toggle IsRunning" HorizontalOptions="Center" /> 
                <Switch x:Name="isVisibleSwitch" IsToggled="True" HorizontalOptions="Center" /> 
                <Label Text="Toggle IsVisible" HorizontalOptions="Center" />
</VerticalStackLayout>

Copy link
Contributor

@jsuarezruiz jsuarezruiz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you include a Device Test for iOS checking the IsAnimating property of the UIActivityIndicatorView using the ActivityIndicator IsRunning and IsVisible properties? https://github.com/dotnet/maui/blob/ef1ec07908eda7b7bf9979fbbf9c374fbe7f2acf/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.iOS.cs

@Domik234
Copy link

Thanks a lot for this PR!

I just wanted to ask if you also tried using HidesWhenStopped = false on construction to leave Hidden attribute control onto developer? I'm asking with regard to the need to call into Obj-C rather than using a managed object (I am not sure how costly is this process).

Thanks for your time. 😀

@bhavanesh2001
Copy link
Contributor Author

bhavanesh2001 commented Apr 16, 2025

@Domik234 As I mentioned. HidesWhenStopped = false will result in a visible activity indicator by default ,but it wont animate(wont spin). This is not expected.

@Domik234
Copy link

@bhavanesh2001 Sorry, I was hasty. I was re-reading if I didn't missed and before I was able to edit, you answered. 😀

@bhavanesh2001
Copy link
Contributor Author

@jfversluis Could you run pipelines.

@jfversluis
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

public partial class ActivityIndicatorHandlerTests : CoreHandlerTestBase<ActivityIndicatorHandler, ActivityIndicatorStub>
{
#if !WINDOWS // On Windows, the platform control will return IsActive as true even when the control is not visible.
[Theory(DisplayName = "IsRunning Should Respect IsVisible")]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything works as expected . But GetNativeIsRunning() seems to return true even when control is not visible. The UI test already covers this scenario.

Screen.Recording.2025-04-17.082414.mp4

@devanathan-vaithiyanathan
Copy link
Contributor

@bhavanesh2001 , Adding the fix in the below places appears to resolve the issue with minimal code changes. Do you think more code changes are necessary?
image

@bhavanesh2001
Copy link
Contributor Author

bhavanesh2001 commented Apr 17, 2025

@bhavanesh2001 , Adding the fix in the below places appears to resolve the issue with minimal code changes. Do you think more code changes are necessary? image

I think It's necessary to map and handle Visiibility like I did in the PR changes

#if __ANDROID__ || IOS || MACCATALYST
			// Since Visibility and IsRunning are dependent on each other, we handle Visibility explicitly.
			[nameof(IActivityIndicator.Visibility)] = MapIsRunning,
#endif

Based on the above changes, I think if I initialize the ActivityIndicator with IsVisible = false and IsRunning = true, and later change IsVisible to true, the indicator won't be shown (or it will be shown but wont animate)—even though at that point both IsVisible and IsRunning are true.

You can test and experiment with IsVisible and IsRunning using the sample code I included in the initial comments.

@devanathan-vaithiyanathan
Copy link
Contributor

@bhavanesh2001 , Adding the fix in the below places appears to resolve the issue with minimal code changes. Do you think more code changes are necessary? image

I think It's necessary to map and handle Visiibility like I did in the PR changes

#if __ANDROID__ || IOS || MACCATALYST
			// Since Visibility and IsRunning are dependent on each other, we handle Visibility explicitly.
			[nameof(IActivityIndicator.Visibility)] = MapIsRunning,
#endif

Based on the above changes, I think if I initialize the ActivityIndicator with IsVisible = false and IsRunning = true, and later change IsVisible to true, the indicator won't be shown (or it will be shown but wont animate)—even though at that point both IsVisible and IsRunning are true.

You can test and experiment with IsVisible and IsRunning using the sample code I included in the initial comments.

Yes @bhavanesh2001 , the mentioned scenario is working fine with my fix. The StartAnimate method is being called from the LayoutSubviews
Here is the demo video

ActivityIndicatorDemo.mov

@bhavanesh2001
Copy link
Contributor Author

@devanathan-vaithiyanathan I think with my changes, we can remove the animation logic from LayoutSubviews. Also, I tried to follow the approach we're already using in the Android case. But feel free to open a PR with your changes as an alternative to this one, so the team can choose the approach they'd prefer to go with.

@bhavanesh2001
Copy link
Contributor Author

/rebase

@jingo0
Copy link

jingo0 commented May 25, 2025

@bhavanesh2001 Will this be addressed in .NET 9, or is it planned for .NET 10?

@bhavanesh2001
Copy link
Contributor Author

@bhavanesh2001 Will this be addressed in .NET 9, or is it planned for .NET 10?

@jingo0 I'm not sure, But if you're facing an issue due to this, you can easily work your way around it.

You can bind IsVisible and IsEnabled to one property so that it hides and shows without any issues.

@jingo0
Copy link

jingo0 commented May 28, 2025

@bhavanesh2001 Will this be addressed in .NET 9, or is it planned for .NET 10?

@jingo0 I'm not sure, But if you're facing an issue due to this, you can easily work your way around it.

You can bind IsVisible and IsEnabled to one property so that it hides and shows without any issues.

Binding IsRunning to IsVisible property did the trick. Surprisingly this issue only occurs on few Views!

@ghost
Copy link

ghost commented Oct 15, 2025

Thanks for the patch — any update on merge status or further changes needed to get this landed? @jsuarezruiz @StephaneDelcroix

@development-jantar
Copy link

@jfversluis I think this is all done, just review is needed. Any chance this could be in next SR for MAUI?

@jfversluis
Copy link
Member

/rebase

@jfversluis jfversluis added this to the .NET 10.0 SR3 milestone Dec 18, 2025
@jfversluis jfversluis moved this to Ready To Review in MAUI SDK Ongoing Dec 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Ready To Review

Development

Successfully merging this pull request may close these issues.

[iOS] [ActivityIndicator] IsRunning ignores IsVisible when set to true

8 participants