From maui-skills
Guides CollectionView implementation in .NET MAUI for scrollable lists/grids with selection, grouping, templates, pull-to-refresh, incremental loading, swipe actions.
npx claudepluginhub davidortinau/maui-skills --plugin maui-skillsThis skill uses the workspace's default tool permissions.
Use `CollectionView` for displaying scrollable lists and grids of data.
Implements CollectionView in .NET MAUI for scrollable lists/grids with data binding, selection, grouping, templates, incremental loading, swipe actions, and pull-to-refresh.
Guides .NET MAUI XAML data bindings including compiled bindings, value converters, binding modes, multi-binding, relative bindings, BindingContext, and MVVM with INotifyPropertyChanged.
Generates .NET MAUI Shell pages, ViewModels, navigation services, source-generated routes, and dialogs using Shiny MAUI Shell. For MAUI apps with advanced Shell navigation, lifecycle hooks, and tab badges.
Share bugs, ideas, or general feedback.
Use CollectionView for displaying scrollable lists and grids of data.
It replaces ListView and offers better performance, flexible layouts, and no ViewCell requirement.
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<HorizontalStackLayout Padding="8" Spacing="8">
<Image Source="{Binding Icon}" WidthRequest="40" HeightRequest="40" />
<Label Text="{Binding Name}" VerticalOptions="Center" />
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
ItemsSource to an ObservableCollection<T> so the UI updates on add/remove.Layout or View — never use ViewCell.x:DataType on DataTemplate for compiled bindings.Commands inside a DataTemplate can't directly reach your ViewModel. Use RelativeSource AncestorType:
<SwipeItem Text="Delete"
BackgroundColor="Red"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:MainViewModel}}, Path=DeleteCommand}"
CommandParameter="{Binding}" />
Wrap CollectionView in a RefreshView. Set IsRefreshing back to false when done:
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CollectionView ItemsSource="{Binding Items}" />
</RefreshView>
<CollectionView ItemsSource="{Binding Items}"
RemainingItemsThreshold="5"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}" />
⚠️ Do NOT use with StackLayout-based ItemsLayout — it has no virtualization and triggers infinite threshold-reached events. Always use
LinearItemsLayoutorGridItemsLayout.
MeasureFirstItem for uniform item sizes — significantly faster than the default MeasureAllItems:
<LinearItemsLayout Orientation="Vertical" ItemSizingStrategy="MeasureFirstItem" />
ObservableCollection<T>, not List<T>. Swapping a List forces a full re-render.MainThread.BeginInvokeOnMainThread(() => Items.Add(item)).| Issue | Fix |
|---|---|
| UI doesn't update when items change | Use ObservableCollection<T>, not List<T>. |
| App crashes or blank items | Never use ViewCell — use Grid, StackLayout, or any View as template root. |
| Items disappear or layout breaks | Always update ItemsSource and the collection on the UI thread (MainThread.BeginInvokeOnMainThread). |
| Incremental loading fires endlessly | Don't use StackLayout as layout; use LinearItemsLayout or GridItemsLayout. |
| EmptyView doesn't render correctly | Wrap custom empty views in ContentView. |
| Poor scroll performance | Use MeasureFirstItem sizing strategy for uniform item sizes. |
| Selected state not visible | Add VisualState Name="Selected" to the item template root element. |
| Binding errors in SwipeView commands | Use RelativeSource AncestorType to reach the ViewModel from inside the item template. |