maui
maui copied to clipboard
[iOS] Rendering of CollectionView takes too much time
Description
Navigate to a page that contains inside a CollectionView. Rendering of the CollectionView takes too much time.
Steps to Reproduce
- Create a page that contains a CollectionView with only 15 items.
- The ItemTemplate should be realistic one, but not mandatory too complex.
- When navigating to the page, the rendering of the CollectionView takes 2-3 seconds.
- If the data of the CollectionView is set (through binding) in OnAppearing, when users tries to navigate to the page, the navigation takes too much, so the user does not see anything happening for 2-3 seconds. If the date is set in OnNavigatedTo, the page is shown right away but the rendering of the CollectionView takes 2-3 seconds.
Link to public reproduction project repository
https://github.com/flesarradu/TestProject-JonChanges
Version with bug
7.0.86
Last version that worked well
Unknown/Other
Affected platforms
iOS, Android, Windows
Affected platform versions
Did you find any workaround?
No
Relevant log output
No response
Dupe of #14591
I am working with the customer that raised this issue. I do not believe it is a dupe of #14591 This is specifically about CollectionView performance. The mentioning of OnNavigatedTo is just to demonstrate how to repo the issue. @unombun can you confirm this?
This is specifically about CollectionView performance
Yes, have you read the entire posts in #14591 ? They mention collectview takes very long to render (when a user navigates to). Exactly what this issue here claims.
Dupe of #14591 I am not sure is really a duplicate of #14591. That one is about performance regarding Shell Navigation, while this one is really focusing on CollectionView performance
I am working with the customer that raised this issue. I do not believe it is a dupe of #14591 This is specifically about CollectionView performance. The mentioning of OnNavigatedTo is just to demonstrate how to repo the issue. @unombun can you confirm this?
Yes, when using OnNavigatedTo the navigation performs well, the CollectionView still does not render properly
We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.
@jonathanpeppers any thoughts/ideas here?
rendering of the CollectionView takes 2-3 seconds.
Is this number from running in Debug or Release mode? Which platform?
If you are seeing it only in Debug mode, you can try UseInterpreter=false
in your .csproj. This will disable C# hot reload, but performance will be generally better.
Looking at your code, I believe this is due to how you've created your view model.
https://github.com/flesarradu/TestProject-JonChanges/blob/master/TestProject/ViewModels/ListPickerViewModel.cs#L29-L44
OnAppearing and OnNavigatedTo are, as far as I know, called on the UI Thread. By having your logic add items to your ObservableCollection in those methods, you're blocking the UI, hence why there is a 2/3 second delay when navigating if you put it on OnAppearing, and "popping" into place with "OnNavigatedTo" (which gets called after the page has been navigated to). If you change your logic sightly, you can load it immediately.
https://github.com/drasticactions/MauiRepros/blob/main/TestProject-Repro/TestProject/ViewModels/ListPickerViewModel.cs#L23-L45
If you run the logic to add the items to the ObservableCollection either when the model is created or on a background thread in OnAppearing, it won't block the UI and the list will appear right away. You can even see your ActivityIndicator still on screen with the list there: The blocker was your code, not CollectionView.
You could also consider:
- No need for
ObservableCollection
if you don't need theCollectionChanged
event to fire -- like a 1 time, filled collection.PropertyChanged
should fire whenAvailableValues
is set. - If the size is known ahead of time, use an array?
Hi @unombun. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.
This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.
So we achieve the worst performance issue on iOS ( We tested in release mode - real device (iPhone)): After testing different workarounds we came with this results:
- Changing from ObservableCollection to List does not make a difference;
- Changing the loading of data in a Task.Run does help in performance. On Android (release mode) is natural, but on iOS still not good as Xamarin.Forms was. There is still a small delay between the navigation happening and the rendering.
- If the setting/loading of data is in the constructor of the ViewModel, there is no delay at all. But this is an ideal situation, something we do not encounter in real life. We depend on passing data through Shell Navigation and based on that passed data we load/compute specific data to be shown in our ViewModel.
Hello lovely human, thank you for your comment on this issue. Because this issue has been closed for a period of time, please strongly consider opening a new issue linking to this issue instead to ensure better visibility of your comment. Thank you!
Reviewing the XAML in this sample, the <DataTemplate>
looks problematic for performance:
<DataTemplate x:DataType="viewModels:ListPickerItemViewModel">
<Grid RowSpacing="0" ColumnSpacing="16"
RowDefinitions="64,1"
HeightRequest="65"
ColumnDefinitions="auto,*">
<Image
Grid.RowSpan="2"
Grid.Column="0"
Margin="16,0,0,0"
IsVisible="{Binding IsPickerValueWithIcon}"
WidthRequest="20"
HeightRequest="20">
<Image.Triggers>
<DataTrigger TargetType="Image" Binding="{Binding IsSelected}" Value="true">
<Setter Property="Source" Value="{Binding IconSelectedFileName}"/>
</DataTrigger>
<DataTrigger TargetType="Image" Binding="{Binding IsSelected}" Value="false">
<Setter Property="Source" Value="{Binding IconNotSelectedFileName}"/>
</DataTrigger>
</Image.Triggers>
</Image>
<VerticalStackLayout
Spacing="0"
Grid.Column="1"
Grid.RowSpan="2"
VerticalOptions="CenterAndExpand">
<Label
Text="{Binding Label}"
FontSize="16">
<Label.Triggers>
<MultiTrigger TargetType="Label">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding IsSelected}" Value="True" />
<BindingCondition Binding="{Binding IsPossibleToChoose}" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="TextColor" Value="LightBlue" />
</MultiTrigger>
<MultiTrigger TargetType="Label">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding IsSelected}" Value="True" />
<BindingCondition Binding="{Binding IsPossibleToChoose}" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="TextColor" Value="DarkGray" />
</MultiTrigger>
<MultiTrigger TargetType="Label">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding IsSelected}" Value="False" />
<BindingCondition Binding="{Binding IsPossibleToChoose}" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="TextColor" Value="DarkGray" />
</MultiTrigger>
<MultiTrigger TargetType="Label">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding IsSelected}" Value="False" />
<BindingCondition Binding="{Binding IsPossibleToChoose}" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="TextColor" Value="DarkGray" />
</MultiTrigger>
</Label.Triggers>
</Label>
<Label
Text="{Binding Description}"
FontSize="12"
TextColor="DarkGrey"
IsVisible="False">
</Label>
</VerticalStackLayout>
<BoxView
Color="DarkGray"
HeightRequest="1"
Margin="16,0,16,0"
Grid.ColumnSpan="2"
Grid.Row="1"/>
</Grid>
</DataTemplate>
That is a lot of logic that will happen for every row.
I removed the <VerticalStackLayout/>
in favor of:
<Label Grid.Column="1" Grid.RowSpan="2" Text="{Binding Label}" FontSize="16" />
And then the CollectionView
pretty much loads instantly. So, what can we do instead?
What I would recommend here is to make a custom view:
- Override
OnBindingContextChanged
- Update all your UI in plain C# code, regular if-else logic, etc.
- Use no data-binding for each row
Here is an example of this technique, although it also uses a hack to only update via OnPropertyChanged(string.Empty)
:
https://github.com/jonathanpeppers/Simulations/blob/main/2D/Simulations.Mobile/OrganismView.cs
If the properties on the objects aren't changing after they are displayed in the CollectionView
, simply using OnBindingContextChanged
might be sufficient for your app.
Reviewing the XAML in this sample, the
<DataTemplate>
looks problematic for performance:<DataTemplate x:DataType="viewModels:ListPickerItemViewModel"> <Grid RowSpacing="0" ColumnSpacing="16" RowDefinitions="64,1" HeightRequest="65" ColumnDefinitions="auto,*"> <Image Grid.RowSpan="2" Grid.Column="0" Margin="16,0,0,0" IsVisible="{Binding IsPickerValueWithIcon}" WidthRequest="20" HeightRequest="20"> <Image.Triggers> <DataTrigger TargetType="Image" Binding="{Binding IsSelected}" Value="true"> <Setter Property="Source" Value="{Binding IconSelectedFileName}"/> </DataTrigger> <DataTrigger TargetType="Image" Binding="{Binding IsSelected}" Value="false"> <Setter Property="Source" Value="{Binding IconNotSelectedFileName}"/> </DataTrigger> </Image.Triggers> </Image> <VerticalStackLayout Spacing="0" Grid.Column="1" Grid.RowSpan="2" VerticalOptions="CenterAndExpand"> <Label Text="{Binding Label}" FontSize="16"> <Label.Triggers> <MultiTrigger TargetType="Label"> <MultiTrigger.Conditions> <BindingCondition Binding="{Binding IsSelected}" Value="True" /> <BindingCondition Binding="{Binding IsPossibleToChoose}" Value="True" /> </MultiTrigger.Conditions> <Setter Property="TextColor" Value="LightBlue" /> </MultiTrigger> <MultiTrigger TargetType="Label"> <MultiTrigger.Conditions> <BindingCondition Binding="{Binding IsSelected}" Value="True" /> <BindingCondition Binding="{Binding IsPossibleToChoose}" Value="False" /> </MultiTrigger.Conditions> <Setter Property="TextColor" Value="DarkGray" /> </MultiTrigger> <MultiTrigger TargetType="Label"> <MultiTrigger.Conditions> <BindingCondition Binding="{Binding IsSelected}" Value="False" /> <BindingCondition Binding="{Binding IsPossibleToChoose}" Value="True" /> </MultiTrigger.Conditions> <Setter Property="TextColor" Value="DarkGray" /> </MultiTrigger> <MultiTrigger TargetType="Label"> <MultiTrigger.Conditions> <BindingCondition Binding="{Binding IsSelected}" Value="False" /> <BindingCondition Binding="{Binding IsPossibleToChoose}" Value="False" /> </MultiTrigger.Conditions> <Setter Property="TextColor" Value="DarkGray" /> </MultiTrigger> </Label.Triggers> </Label> <Label Text="{Binding Description}" FontSize="12" TextColor="DarkGrey" IsVisible="False"> </Label> </VerticalStackLayout> <BoxView Color="DarkGray" HeightRequest="1" Margin="16,0,16,0" Grid.ColumnSpan="2" Grid.Row="1"/> </Grid> </DataTemplate>
That is a lot of logic that will happen for every row.
I removed the
<VerticalStackLayout/>
in favor of:<Label Grid.Column="1" Grid.RowSpan="2" Text="{Binding Label}" FontSize="16" />
And then the
CollectionView
pretty much loads instantly. So, what can we do instead?What I would recommend here is to make a custom view:
* Override `OnBindingContextChanged` * Update all your UI in plain C# code, regular if-else logic, etc. * Use no data-binding for each row
Here is an example of this technique, although it also uses a hack to only update via
OnPropertyChanged(string.Empty)
:https://github.com/jonathanpeppers/Simulations/blob/main/2D/Simulations.Mobile/OrganismView.cs
If the properties on the objects aren't changing after they are displayed in the
CollectionView
, simply usingOnBindingContextChanged
might be sufficient for your app.
I do not want to sound disrespectful, but if a multi trigger is "a lot of logic" and your workaround is to create a custom control and writing UI logic in C# without using bindings, then what is the point of MAUI then? You could also write something like use Flutter or ReactNative :( . This is extremely disappointing taking into account that this exact code worked just fine in Xamarin.Forms. .Net Maui should have been an improvement of Xamarin with better performance and better memory management. In the end is quite the opposite.
A datatemplate with ~80 lines of XAML and 4 multi-triggers is not going to perform well on mobile. Even if we improved it, it will still be an order of magnitude worse than setting a few properties in C#.
This is what performance work looks like -- you find the "hot spots" in your application, and rewrite using a faster/more performant way.
Verified this issue with Visual Studio Enterprise 17.9.0 Preview 3. Can repro on Android platforms with sample project. https://github.com/flesarradu/TestProject-JonChanges
Related https://github.com/dotnet/maui/issues/18505
@BenBtg are you still seeing issues with this one on iOS?
Could this be related?
https://github.com/dotnet/maui/issues/19383
Rendering performance of CollectionView disappoints on both Android and iOS (also still in 8.0.60). See also #21580
When can we expect the performance issues of this collection view to be resolved? Please let us know. If it takes too long, we will need to switch to another cross-platform solution as soon as possible.
@Equabyte @xyeie can you share a sample or .speedscope
file for us to investigate? https://aka.ms/profile-maui
If something is slow, (with no details) it could be literally anything and not MAUI's fault. I would also verify you are testing performance in Release
mode.
@Equabyte @xyeie can you share a sample or
.speedscope
file for us to investigate? https://aka.ms/profile-mauiIf something is slow, (with no details) it could be literally anything and not MAUI's fault. I would also verify you are testing performance in
Release
mode.
https://github.com/xyeie/XinYeMobile I did a demo project, and in release mode it was very slow to navigate to another page. After entering the page, sliding left and right to switch is also more awkward
@xyeie what platform? iOS-only?
Your description feels more like navigation was implemented differently in MAUI, than a performance issue.
@xyeie what platform? iOS-only?
Your description feels more like navigation was implemented differently in MAUI, than a performance issue.
Android。1. On Android platform, Navigating to a page with a collection view is slow. I think it's because the collection view takes too long. 2. The performance of the iOS platform is good.
@Equabyte @xyeie can you share a sample or
.speedscope
file for us to investigate? https://aka.ms/profile-maui
Hi @jonathanpeppers my issue is specific to the infinite scrolling use case. Can't find time these days to prepare a sample project so I'm sharing here a video showing what I mean, in this case on Android, compiled with 8.0.60. I'll keep it public for a few days so you can access it (too big to upload here).
@Equabyte thanks! Watching your video, does it scroll faster if you comment out the images? If so, one thing we could check is how you load them, and if they are larger (downscaled) than the space they occupy on screen. Android is notoriously slow when dealing with images...
The second part you mention, the "infinite scroll" feature. We might need a sample to understand what's happening there.
@Equabyte thanks! Watching your video, does it scroll faster if you comment out the images? If so, one thing we could check is how you load them, and if they are larger (downscaled) than the space they occupy on screen. Android is notoriously slow when dealing with images...
The second part you mention, the "infinite scroll" feature. We might need a sample to understand what's happening there.
Setting all images to null does not seem to address the root cause and there is no massive difference (that was an easy test to do because the CollectionView has a template selector that takes care of the dual layout, for headline news with and without image). I'm under the impression the CV item rendering itself (maybe the item measurement part?) is somehow taking longer than it should. In this case I need to use MeasureAllItems because it's part of my requirements to have no homogeneous items, but I'm only adding a few items at a time. I'd like to provide a sample project but these days I'm stuck with something else.