.NET MAUI FAQ - Collection View

Find answers for the most frequently asked questions
Expand All Collapse All

It is better to use lightweight item view models implementing INotifyPropertyChanged. They provide predictable UI updates, support local UI state, and keep domain models free of UI responsibilities.

public class ItemVM : INotifyPropertyChanged
{
  public string Title { get; set; }
  /*...*/
}

public ObservableCollection<ItemVM> Items { get; } = new ObservableCollection<ItemVM>();

<CollectionView ItemsSource="{Binding Items}" />
Permalink

Track the last visible index using ItemsViewScrolled. Auto-scrolling only when the user is within a certain threshold of the bottom ( e.g., the last two or three items prevents the interruption of active scrolling.

void OnItemsViewScrolled(object s, ItemsViewScrolledEventArgs e)
{
  var lastIndex = Items.Count - 1;
  bool nearBottom = (lastIndex - e.LastVisibleItemIndex) <= 2;
  if (nearBottom) collectionView.ScrollTo(Items.Last(), position: ScrollToPosition.End, animate: true);
}
Permalink

For long, image-heavy feeds, use element recycling, batched data updates, downsampled and cached images, lightweight item templates, and background image loading. These reduce UI workload and keep the CollectionView scrolling smoothly even with large image feeds.

<CollectionView ItemsLayout="VerticalList"
                ItemSizingStrategy="MeasureFirstItem"
                RecycleElement="True"
                RemainingItemsThreshold="3"
                RemainingItemsThresholdReached="OnLoadMore">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <Grid HeightRequest="240">
                <!-- Use UriImageSource to enable caching; constrain size for downsampling -->
                <Image Aspect="AspectFill"
                       HeightRequest="240"
                       WidthRequest="400">
                    <Image.Source>
                        <UriImageSource Uri="{Binding ThumbnailUrl}"
                                        CachingEnabled="True"
                                        CacheValidity="7.00:00:00" />
                    </Image.Source>
                </Image>
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>
Permalink

MAUI CollectionView is powerful and tunable. However, in extremely heavy scenarios (rich templates, frequent gestures, continuous paging), Syncfusion SfListView often performs better due to its optimized virtualization and load-on-demand mechanisms. It becomes a preferable option if MAUI tuning does not meet the performance needs.

<CollectionView SelectionMode="Single" ItemsSource="{Binding Items}">
  <CollectionView.ItemTemplate>
    <DataTemplate>
      <Grid x:Name="root">
        <VisualStateManager.VisualStateGroups>
          <VisualStateGroup Name="SelectionStates">
            <VisualState Name="Normal" />
            <VisualState Name="Selected">
              <VisualState.Setters>
                <Setter TargetName="root" Property="BackgroundColor" Value="LightBlue"/>
              </VisualState.Setters>
            </VisualState>
          </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Label Text="{Binding Title}"/>
      </Grid>
    </DataTemplate>
  </CollectionView.ItemTemplate>
</CollectionView>
Permalink

Use a single bulk update (such as ObservableRangeCollection.AddRange) or replace the ItemsSource entirely. Enable RecycleElement, down-sample and cache images, move heavy work off the UI thread, and keep templates lightweight.

public ObservableRangeCollection<string> Items { get; } = new();
public void LoadItems_Correct()
{
    var list = new List<string> { "A", "B", "C", "D", "E" };

    Items.AddRange(list); // Single UI update, much faster
}
Permalink

Capture the first visible item (or its index) and offset. Insert new messages using a batch update, then restore scroll position using ScrollTo(savedItem/index, ScrollToPosition.Start, animate: false). This preserves the viewport and avoids jumping.

// before insert
var firstVisibleIndex = args.FirstVisibleItemIndex; // from ItemsViewScrolled
var firstVisibleItem = view.ItemsSource.Cast<object>().ElementAt(firstVisibleIndex);

// insert batch at index 0
items.InsertRange(0, newItems);

// restore
collectionView.ScrollTo(firstVisibleItem, position: ScrollToPosition.Start, animate: false);
Permalink

CollectionView recycles its item containers. When ItemsSource changes, existing visuals may be reused before new bindings are applied. Behaviors that cached item references when they were first attached will then reference stale or null objects.

Fix: Use bindings based on the current BindingContext (such as {Binding .}), avoid storing item references inside the behavior, and reapply or update behaviors when containers are reused.

<local:TapBehavior Command="{Binding TapCommand}"
                   CommandParameter="{Binding .}" />
Permalink

Share with

Couldn't find the FAQs you're looking for?

Please submit your question and answer.