dotnet/maui · error · ArgumentException
Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' i
Error message
Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' is greater than the number of rows '{lastIndex}'. What it means
ListViewRenderer's CellsOnCollectionChanged validates that a NotifyCollectionChangedEventArgs index (NewStartingIndex/OldStartingIndex) does not exceed the UITableView's current row count for the section. This only fires for non-grouped, RetainElement lists. The check exists because iOS UITableView will crash if asked to insert/update rows past its reported count - so MAUI surfaces a clearer ArgumentException first.
Source
Thrown at src/Compatibility/Core/src/iOS/Renderers/ListViewRenderer.cs:568
{
var exArgs = e as NotifyCollectionChangedEventArgsEx;
if (exArgs != null)
_dataSource.Counts[section] = exArgs.Count;
// This means the UITableView hasn't rendered any cells yet
// so there's no need to synchronize the rows on the UITableView
if (Control.IndexPathsForVisibleRows == null && e.Action != NotifyCollectionChangedAction.Reset)
return;
var groupReset = resetWhenGrouped && Element.IsGroupingEnabled;
// We can't do this check on grouped lists because the index doesn't match the number of rows in a section.
// Likewise, we can't do this check on lists using RecycleElement because the number of rows in a section will remain constant because they are reused.
if (!groupReset && Element.CachingStrategy == ListViewCachingStrategy.RetainElement)
{
var lastIndex = Control.NumberOfRowsInSection(section);
if (e.NewStartingIndex > lastIndex || e.OldStartingIndex > lastIndex)
throw new ArgumentException(
$"Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' is greater than the number of rows '{lastIndex}'.");
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewStartingIndex == -1 || groupReset)
goto case NotifyCollectionChangedAction.Reset;
InsertRows(e.NewStartingIndex, e.NewItems.Count, section);
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldStartingIndex == -1 || groupReset)
goto case NotifyCollectionChangedAction.Reset;
DeleteRows(e.OldStartingIndex, e.OldItems.Count, section);View on GitHub (pinned to f377ff1c5e)
Solutions
- Always mutate the bound ObservableCollection on the main UI thread.
- For bulk edits, use a single Reset notification or replace the entire ItemsSource rather than per-item Add with indices.
- Switch the ListView to RecycleElement (the check is skipped for that strategy) if appropriate.
- If raising collection changes manually, ensure NewStartingIndex/OldStartingIndex are accurate and within bounds.
Example fix
// before _items.Add(item); // fired from background thread with stale index // after Device.BeginInvokeOnMainThread(() => _items.Add(item)); // or for bulk: _items = new ObservableCollection<Item>(newItems); list.ItemsSource = _items;
Defensive patterns
Strategy: validation
Validate before calling
// Ensure all ItemsSource mutations occur on the main thread: Device.BeginInvokeOnMainThread(() => collection.Add(item));
Try / catch
try { /* navigation/mutation */ }
catch (ArgumentException ex) when (ex.Message.Contains("number of rows"))
{
// collection state desynced; refresh the source
list.ItemsSource = null;
list.ItemsSource = collection;
} Prevention
- Mutate bound collections only on the UI thread.
- Prefer replacing ItemsSource for bulk edits over index-based adds.
- Avoid mixing RetainElement with rapidly-changing sources.
When it happens
Trigger: An ObservableCollection bound to ListView.ItemsSource raises a collection-changed event with a NewStartingIndex or OldStartingIndex larger than the number of rows currently in that section. Common with batched/range edits, multi-threaded mutation, or an incorrect NotifyCollectionChangedEventArgs constructed manually.
Common situations: Calling CollectionChanged with Reset-vs-Add semantics incorrectly; mutating the source collection off the main thread; replacing the ItemsSource while a pending change is in flight; using RetainElement with a source that reports stale indices.
Related errors
- Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' i
- Header cells do not support context actions
- Implement INativeElementView on cell renderer: {ContentCell.
- No UIViewController found to present.
- Header cells do not support context actions
AI-assisted analysis of dotnet/maui@f377ff1c5e (2026-08-13).
Data as JSON: /api/errors/644fc6283ca1427e.
Report an issue: GitHub.