Xamarin.Forms/MAUI CollectionView performance issue: it's your own damn fault.
So I have seen a lot of people complaining about the performance of the CollectionView
on Xamarin.Forms
and soon it will be the same with Maui
...
If you have a laggy list, it's true there are some issues with the implementation of vanilla XF CollectionView
(especially with DataTemplateSelector
and the recycling of the selected DataTemplate
...),
The collection view doesn't magically optimize all of your bloated views, you have to follow some basic rules if you want a slick smooth scrolling...
So here are 4 golden rules to have a smooth scroll with your collections.
USE FIXED HEIGHT FOR YOUR ITEMS !!!!
This is the most important one, if you don't specify a fixed height for your items, each time a view will be recycled, it will make a whole layout calculation on your view to compute its size! This is nuts.
Oh... Oh But I know what you will say:
"boohooh, but my designer wants that all cells size automatically depending on its content, boohooohhh, cry, cry...".
Stop that! Stay strong folks! Stand-up to your designer!
Then tell him: "ok! no pro bro :-)
it will size automatically dude <3
...but you will have a GODDAMN SHITTY LAGGING LIST!"
After that your designer should soften and you can make peace with him over a fixed size per DataTemplateSelector
's templates by using the Sharpnado.CollectionView
:
<views:HeaderFooterGroupingTemplateSelector
x:Key="HeaderFooterGroupingTemplateSelector"
DudeTemplate="{StaticResource DudeTemplate}"
FooterTemplate="{sho:SizedDataTemplate
Template={StaticResource FooterTemplate},
Size=60}"
GroupHeaderTemplate="{sho:SizedDataTemplate
Template={StaticResource GroupHeaderTemplate},
Size=75}"
HeaderTemplate="{sho:SizedDataTemplate
Template={StaticResource HeaderTemplate},
Size=40}" />
If you can live with a fixed size of all the items, you can use the vanilla CollectionView
but don't forget to specify the MeasureFirstItem
value for your sizing strategy.
Careful with Data Binding
Data binding is in fact, very costly, and very often you don't set your binding mode explicitly. And then it defaults to OneWay
, which means that it will listen to all changes coming from your source (your view model).
BUT, most of the time, you will display readonly data on your list, so use OneTime
binding mode instead, you silly
Don't forget also to use compiled binding on your data template, it will also boost significantly your binding performance (and give you intellisense on your item views).
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.CompiledColorListPage"
Title="Compiled Color List">
<Grid>
...
<ListView x:Name="colorListView"
ItemsSource="{x:Static local:NamedColor.All}"
... >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:NamedColor">
<ViewCell>
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
... />
<Label Text="{Binding FriendlyName}"
... />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- The BoxView doesn't use compiled bindings -->
<BoxView Color="{Binding Source={x:Reference colorListView},
Path=SelectedItem.Color}"
... />
</Grid>
</ContentPage>
Update your whole item, not some properties
If the list your using is implemented with a RecyclerView
on Android
and UICollectionView
on iOS
(which is the case of Sharpnado.CollectionView
and vanilla CollectionView
), it will be much much more efficient to replace the whole item, than updating some properties through binding.
The swap, insertion, or whatever change, will then be handled at the native level, even with a nice (insertion/deletion) animation.
If you only change one item at a time, use the ObservableCollection
if you are changing several items (batch deletion or insertion), use the ObservableRangeCollection
in the sharpnado's package, or the one in the XamarinCommunityToolkit.
public ObservableCollection<TourSummary> TourSummaries { get; set; }
= new();
private void OnTourSummaryUpdated(TourSummary tourSummary)
{
int index = _tourSummaries.IndexOf(c => c.Id == tourSummary.Id);
if (index > -1)
{
// replacing your item
TourSummaries[index] = tourSummary;
}
else
{
// adding at the end
TourSummaries.Add(tourSummary);
}
}
It's even easier from a business point of view, instead of having UpdateTourDate
, UpdateTourDescription
, etc, methods in your service layer, you just need a UpdateTour
method, updating the whole object.
View nesting
Careful with nesting two many views, prefer flat views with Grid
layout, but if you are StackLayout
nesting addict, don't forget that you can compress all your nested views into a flat view thanks to the CompressedLayout.IsHeadless
property!