TheBetterListView.MAUI 1.0.1

dotnet add package TheBetterListView.MAUI --version 1.0.1
                    
NuGet\Install-Package TheBetterListView.MAUI -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TheBetterListView.MAUI" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TheBetterListView.MAUI" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="TheBetterListView.MAUI" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TheBetterListView.MAUI --version 1.0.1
                    
#r "nuget: TheBetterListView.MAUI, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TheBetterListView.MAUI@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TheBetterListView.MAUI&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=TheBetterListView.MAUI&version=1.0.1
                    
Install as a Cake Tool

TheBetterListView.MAUI

A virtualized, recycling items view for .NET MAUI with deterministic cell sizing — grid and list layouts from one control.

Why

MAUI's CollectionView on iOS wraps UICollectionView and relies on UIKit's self-sizing cell dequeue. During recycle, MAUI can measure a cell before its async content (e.g. an AspectFit image) has decoded, feeding a collapsed height to the flow layout — the cell renders as a thin horizontal strip. Pinning an exact HeightRequest on the template masks the collapse but makes GridItemsLayout drift and overlap rows. It is an implementation bug, not a tuning problem.

BetterItemsView owns its layout math instead:

  • No collapse, by construction — the cell frame height is always exactly ItemHeight; it never depends on measuring item content.
  • No overlap, by construction — every cell position is computed arithmetically from its index (row = index / ColumnCount); two indices can never produce intersecting rects.
  • True virtualization — only visible cells (+ BufferRows of overscan) are realized; views are pooled and recycled by rebinding BindingContext. Realized count stays constant regardless of item count.
  • No layout passes while scrolling — recycled cells are positioned with TranslationX/Y, which doesn't invalidate layout.
  • Pure shared code — a ScrollView over an AbsoluteLayout canvas; no per-platform handlers, so iOS, Android, MacCatalyst, and Windows all run the same implementation.

Install

dotnet add package TheBetterListView.MAUI

No UseXxx() builder registration is needed — there are no custom handlers.

API

Member Type Default Notes
ItemsSource IEnumerable (bindable) null Observes INotifyCollectionChanged when implemented. Reassigning the whole source resets scroll to top (matches CollectionView).
ItemTemplate DataTemplate (bindable) null Must create a View (not a Cell). Rebinding on recycle re-evaluates DataTriggers, so model-driven selection templates work unchanged.
ColumnCount int (bindable) 1 1 = list layout. Supports {OnIdiom ...}. Avoid changing it at runtime on rotation — fixed spans keep rotation smooth.
ItemHeight double (bindable) 100 The exact, always-honored cell height. Choose it from your template's known structure.
RowSpacing double (bindable) 0 Vertical gap between rows.
ColumnSpacing double (bindable) 0 Horizontal gap between columns; cell width = (viewportWidth − gaps) / ColumnCount.
BufferRows int (bindable) 2 Overscan rows realized above and below the viewport.
RealizedCellCount int (read-only) Diagnostics: current pool-in count, for verifying bounded realization.

Grid example

xmlns:tblv="clr-namespace:TheBetterListView.MAUI;assembly=TheBetterListView.MAUI"

<RefreshView Refreshing="OnRefreshing">
    <tblv:BetterItemsView ItemsSource="{Binding Items}"
                          ColumnCount="{OnIdiom Phone=3, Default=4}"
                          ItemHeight="155" RowSpacing="12" ColumnSpacing="12">
        <tblv:BetterItemsView.ItemTemplate>
            <DataTemplate x:DataType="models:MyItem">
                <Border Margin="10">
                    <Grid RowDefinitions="*,20">
                        <Image Source="{Binding Icon}" Aspect="AspectFit" HeightRequest="100" />
                        <Label Grid.Row="1" Text="{Binding Title}" LineBreakMode="TailTruncation" />
                    </Grid>
                </Border>
            </DataTemplate>
        </tblv:BetterItemsView.ItemTemplate>
    </tblv:BetterItemsView>
</RefreshView>

List example

<tblv:BetterItemsView ItemsSource="{Binding Items}" ColumnCount="1" ItemHeight="70">
    <tblv:BetterItemsView.ItemTemplate>
        <DataTemplate x:DataType="models:MyItem">
            <Grid Padding="0,5" ColumnDefinitions="70,*">
                <Image Source="{Binding Icon}" WidthRequest="30" HeightRequest="30" />
                <Label Grid.Column="1" Text="{Binding Title}" VerticalOptions="Center" />
            </Grid>
        </DataTemplate>
    </tblv:BetterItemsView.ItemTemplate>
</tblv:BetterItemsView>

Swapping in for an existing CollectionView

The binding surface is intentionally close, so a swap is mostly mechanical:

  1. Keep any wrapping RefreshView — pull-to-refresh works because the control's visual tree contains a native scroll view.
  2. Move the DataTemplate over verbatim — gestures (TapGestureRecognizer, PointerGestureRecognizer), DataTriggers, badges, and converters all keep working; they live in plain MAUI views.
  3. Map GridItemsLayout SpanColumnCount, Horizontal/VerticalItemSpacingColumnSpacing/RowSpacing. LinearItemsLayoutColumnCount="1".
  4. Pick an explicit ItemHeight from the template's known structure (e.g. image 100 + title 20 + paddings/margins). This replaces auto-sizing — that determinism is the fix.
  5. Code that reads ItemsSource back off the view keeps working — the property returns the same object instance you assigned.
  6. Converters that receive the view via ConverterParameter={x:Reference ...} and cast to CollectionView must learn to accept BetterItemsView too (its ItemsSource property is all they usually need).

Behavior notes

  • Reassigning ItemsSource (e.g. navigating between folders) resets scroll to the top.
  • ItemTemplate must produce a View; a Cell throws InvalidOperationException.
  • All items share one cell size. Uneven/self-sized content inside the cell is fine (it lays out within the fixed frame); varying cell heights are out of scope by design.
  • Collection changes trigger a cheap rebind of visible cells only (a few dozen views), regardless of collection size.

Development

  • Tests/ — xUnit suite for the virtualization math (VirtualGridCalculator) and source tracking (ItemsSourceTracker): cd Tests && dotnet test.
  • Sibling repo TheBetterListViewTestApp — interactive harness: 300 mixed items, grid/list toggle, long-press selection, pull-to-refresh, wholesale source swaps, realized-cell diagnostics.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-android36.0 is compatible.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-ios26.0 is compatible.  net10.0-maccatalyst was computed.  net10.0-maccatalyst26.0 is compatible.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net10.0-windows10.0.19041 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 118 7/13/2026
1.0.0 107 7/13/2026