Skip to content

Fix iOS CollectionView stale layout invalidations#35245

Merged
kubaflo merged 4 commits into
dotnet:inflight/currentfrom
filipnavara:cv-invalid-measurements
May 8, 2026
Merged

Fix iOS CollectionView stale layout invalidations#35245
kubaflo merged 4 commits into
dotnet:inflight/currentfrom
filipnavara:cv-invalid-measurements

Conversation

@filipnavara
Copy link
Copy Markdown
Member

Description of Change

There are two related races in the iOS CollectionView handler when the item source changes while cells are still participating in UIKit layout.

The first race happens after a visible templated cell invalidates its measure. MAUI records that state on the cell and the next ViewWillLayoutSubviews pass asks UICollectionViewFlowLayout to invalidate exactly those changed items. Previously the handler collected the cells first and converted them back to index paths later, at the point where the invalidation context was created. If the bound ItemsSource had changed in the meantime, for example an observable source inserted items and the view then cleared or replaced ItemsSource before the next layout pass, UIKit could still report a visible cell whose current index path no longer existed in MAUI's new ItemsSource. Passing that stale index path to InvalidateItems leaves UICollectionView and the data source with inconsistent item counts and can crash during layout.

Fix that path by resolving each invalidated visible cell to an NSIndexPath immediately and keeping only paths that are still valid for the current ItemsSource. The invalidation context is then built from the validated paths. This preserves targeted invalidation for normal measure changes, while dropping cells that belong to the previous source state and cannot be safely invalidated by item path anymore.

The second race involves measurement cells. ItemsViewController keeps prototype templated cells in _measurementCells so their realized content can be transferred to real UICollectionView cells. When an ItemsSource update, empty-source transition, or source disposal clears that dictionary, the old code only removed the references. Those measurement cells could still be bound to item view models and still subscribed to LayoutAttributesChanged. Later binding or property changes from that stale content could propagate measure/layout invalidations through cells that are no longer owned by the active source state. In the worst case this combines with UIKit's pending layout work after ReloadData or source clearing and contributes to the same stale layout invalidation problem; it can also keep disconnected measurement content behaving as if it were still live.

Fix that by centralizing measurement-cell clearing. Before the cache is cleared, each cached cell is detached from the layout-attribute event and unbound so its BindingContext is removed and future measure invalidations from that stale measured content do not flow back into the CollectionView layout.

The regression test reproduces the important ordering: a templated CollectionView is displayed, a visible label changes text so the cell measure is invalidated, the observable source mutates, and ItemsSource is immediately cleared before UIKit finishes its next layout pass. The test forces layout afterward and verifies this no longer crashes.

Issues Fixed

Fixes #35244

There are two related races in the iOS CollectionView handler when the item source changes while cells are still participating in UIKit layout.

The first race happens after a visible templated cell invalidates its measure. MAUI records that state on the cell and the next ViewWillLayoutSubviews pass asks UICollectionViewFlowLayout to invalidate exactly those changed items. Previously the handler collected the cells first and converted them back to index paths later, at the point where the invalidation context was created. If the bound ItemsSource had changed in the meantime, for example an observable source inserted items and the view then cleared or replaced ItemsSource before the next layout pass, UIKit could still report a visible cell whose current index path no longer existed in MAUI's new ItemsSource. Passing that stale index path to InvalidateItems leaves UICollectionView and the data source with inconsistent item counts and can crash during layout.

Fix that path by resolving each invalidated visible cell to an NSIndexPath immediately and keeping only paths that are still valid for the current ItemsSource. The invalidation context is then built from the validated paths. This preserves targeted invalidation for normal measure changes, while dropping cells that belong to the previous source state and cannot be safely invalidated by item path anymore.

The second race involves measurement cells. ItemsViewController keeps prototype templated cells in _measurementCells so their realized content can be transferred to real UICollectionView cells. When an ItemsSource update, empty-source transition, or source disposal clears that dictionary, the old code only removed the references. Those measurement cells could still be bound to item view models and still subscribed to LayoutAttributesChanged. Later binding or property changes from that stale content could propagate measure/layout invalidations through cells that are no longer owned by the active source state. In the worst case this combines with UIKit's pending layout work after ReloadData or source clearing and contributes to the same stale layout invalidation problem; it can also keep disconnected measurement content behaving as if it were still live.

Fix that by centralizing measurement-cell clearing. Before the cache is cleared, each cached cell is detached from the layout-attribute event and unbound so its BindingContext is removed and future measure invalidations from that stale measured content do not flow back into the CollectionView layout.

The regression test reproduces the important ordering: a templated CollectionView is displayed, a visible label changes text so the cell measure is invalidated, the observable source mutates, and ItemsSource is immediately cleared before UIKit finishes its next layout pass. The test forces layout afterward and verifies this no longer crashes.
Copilot AI review requested due to automatic review settings April 30, 2026 08:22
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35245

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35245"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Addresses iOS CollectionView layout invalidation races when the ItemsSource changes during/around UIKit layout passes, aiming to prevent stale index-path invalidations and detach stale measurement cells to avoid post-clear invalidations and crashes.

Changes:

  • Validates visible-cell index paths before building a targeted UICollectionViewFlowLayoutInvalidationContext.
  • Centralizes measurement-cell cache clearing to also detach event handlers and unbind cached cells.
  • Adds an iOS device regression test that exercises “measure invalidation + ItemsSource mutation + ItemsSource clear” ordering.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs Filters/records index paths earlier during layout invalidation and introduces ClearMeasurementCells() to detach/unbind cached measurement cells.
src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs Adds a regression test intended to reproduce the stale invalidation crash scenario.

Comment thread src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
Comment thread src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs Outdated
Comment thread src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs Outdated
Comment thread src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a race in the iOS CollectionView handlers where pending UIKit layout passes can attempt targeted invalidations using stale index paths after an ItemsSource change, potentially crashing during layout. Also ensures measurement/prototype cells are properly detached/unbound when clearing their cache to prevent stale invalidations from orphaned content.

Changes:

  • Validate and capture NSIndexPath values immediately for MeasureInvalidated visible cells (dropping stale paths) before calling InvalidateItems.
  • Centralize and harden measurement-cell cache clearing by detaching LayoutAttributesChanged and unbinding cached cells before clearing.
  • Add a device regression test that exercises the stale-invalidation timing (runs against both CollectionViewHandler and CollectionViewHandler2).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs Validates index paths before invalidation and safely clears measurement cells by unbinding/detaching.
src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs Applies the same index-path validation approach to the Handler2 iOS implementation.
src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs Adds a regression device test for the stale invalidation crash scenario for both handlers.

Comment thread src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs Outdated
Comment thread src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.

Why: The PR's Gate failed on iOS device tests, making try-fix-2 the best validated alternative. try-fix-2's early-exit source-empty guard (if (ItemsSource.ItemCount == 0) return) is the simplest passing fix — 14 lines across 2 files — that directly prevents the crash at the exact UIKit contract violation point. Two other try-fix approaches also passed, but try-fix-2 is the most minimal with zero side effects.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-2`)
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
index 04d41a5607..dfda1143dd 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
@@ -256,6 +256,13 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
		void InvalidateLayoutIfItemsMeasureChanged()
		{
+			// When the source has no items, any visible cells with MeasureInvalidated=true belong to the
+			// previous source and are still animating out. Passing their stale index paths to
+			// InvalidateItems would throw NSInternalInconsistencyException because UIKit has already
+			// committed the reload to zero items.
+			if (ItemsSource.ItemCount == 0)
+				return;
+
 			var visibleCells = CollectionView.VisibleCells;
 			List<TemplatedCell> invalidatedCells = null;
 
diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
index a92f32bc46..f5e854885c 100644
--- a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
+++ b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
@@ -209,6 +209,13 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 
		void InvalidateLayoutIfItemsMeasureChanged()
		{
+			// When the source has no items, any visible cells with MeasureInvalidated=true belong to the
+			// previous source and are still animating out. Passing their stale index paths to
+			// InvalidateItems would throw NSInternalInconsistencyException because UIKit has already
+			// committed the reload to zero items.
+			if (ItemsSource.ItemCount == 0)
+				return;
+
 			var collectionView = CollectionView;
 			var visibleCells = collectionView.VisibleCells;
 			List<TemplatedCell2> invalidatedCells = null;

@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Apr 30, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please try ai's suggestions?

@dotnet dotnet deleted a comment from MauiBot May 1, 2026
@kubaflo kubaflo dismissed MauiBot’s stale review May 1, 2026 09:39

Resetting for re-review

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 7 findings

See inline comments for details.

@MauiBot MauiBot added s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels May 1, 2026
@dotnet dotnet deleted a comment from MauiBot May 2, 2026
@dotnet dotnet deleted a comment from MauiBot May 2, 2026
@dotnet dotnet deleted a comment from MauiBot May 2, 2026
@dotnet dotnet deleted a comment from MauiBot May 2, 2026
@dotnet dotnet deleted a comment from MauiBot May 2, 2026
@dotnet dotnet deleted a comment from MauiBot May 2, 2026
@dotnet dotnet deleted a comment from MauiBot May 2, 2026
@dotnet dotnet deleted a comment from MauiBot May 2, 2026
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 5 findings

See inline comments for details.

ItemsViewLayout.PrepareCellForLayout(cell);
}

void ClearMeasurementCells()
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is generally true also for non-measurement cells and possible wider scope bug that should be fixed separately. (In our app we diligently reset ItemSource to null to solve this.)

Comment thread src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs Outdated
@dotnet dotnet deleted a comment from MauiBot May 6, 2026
@dotnet dotnet deleted a comment from MauiBot May 6, 2026
@dotnet dotnet deleted a comment from MauiBot May 6, 2026
@dotnet dotnet deleted a comment from MauiBot May 6, 2026
@dotnet dotnet deleted a comment from MauiBot May 6, 2026
MauiBot
MauiBot previously requested changes May 6, 2026
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.

Why: try-fix-2 is the only empirically validated candidate (923/923 device tests passing). It takes a prevention-over-filtering approach: by clearing NeedsCellLayout before the source swap, InvalidateLayoutIfItemsMeasureChanged never runs during the transitional state, eliminating the stale-index-path crash at its root. A source-generation counter provides lightweight lazy invalidation for stale measurement-cell callbacks without requiring eager unbind of all cells.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-2`)
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
index 04d41a5607..80f62912d2 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
@@ -45,6 +45,8 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 		UIView _emptyUIView;
 		VisualElement _emptyViewFormsElement;
 		Dictionary<object, TemplatedCell> _measurementCells = new Dictionary<object, TemplatedCell>();
+		readonly Dictionary<TemplatedCell, int> _measurementCellGenerations = new();
+		int _sourceGeneration = 0;
 		List<string> _cellReuseIds = new List<string>();
 
 		[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")]
@@ -419,6 +421,9 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
 		public virtual void UpdateItemsSource()
 		{
+			if (CollectionView is MauiCollectionView mauiCV)
+				mauiCV.NeedsCellLayout = false;
+			_sourceGeneration++;
 			_measurementCells?.Clear();
 			ItemsViewLayout?.ClearCellSizeCache();
 			ItemsSource?.Dispose();
@@ -432,6 +437,9 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
 		internal void DisposeItemsSource()
 		{
+			if (CollectionView is MauiCollectionView mauiCV)
+				mauiCV.NeedsCellLayout = false;
+			_sourceGeneration++;
 			_measurementCells?.Clear();
 			ItemsViewLayout?.ClearCellSizeCache();
 			ItemsSource?.Dispose();
@@ -494,6 +502,9 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 		protected virtual void UpdateTemplatedCell(TemplatedCell cell, NSIndexPath indexPath)
 		{
 			cell.LayoutAttributesChanged -= CellLayoutAttributesChanged;
+			// If this cell was previously a measurement cell in an older generation, remove its stale entry
+			// so its future LayoutAttributesChanged events are processed normally in the new source context.
+			_measurementCellGenerations.Remove(cell);
 
 			var bindingContext = ItemsSource[indexPath];
 
@@ -501,6 +512,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 			if (_measurementCells != null && _measurementCells.TryGetValue(bindingContext, out TemplatedCell measurementCell))
 			{
 				_measurementCells.Remove(bindingContext);
+				_measurementCellGenerations.Remove(measurementCell);
 				measurementCell.LayoutAttributesChanged -= CellLayoutAttributesChanged;
 				cell.UseContent(measurementCell);
 			}
@@ -527,6 +539,14 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 		[UnconditionalSuppressMessage("Memory", "MEM0003", Justification = "Proven safe in test: CollectionViewTests.ItemsSourceDoesNotLeak")]
 		void CellLayoutAttributesChanged(object sender, LayoutAttributesChangedEventArgs args)
 		{
+			if (sender is TemplatedCell cell &&
+				_measurementCellGenerations.TryGetValue(cell, out int gen) &&
+				gen != _sourceGeneration)
+			{
+				_measurementCellGenerations.Remove(cell);
+				return;
+			}
+
 			CacheCellAttributes(args.NewAttributes.IndexPath, args.NewAttributes.Size);
 		}
 
@@ -902,7 +922,10 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
 			// Keep this cell around, we can transfer the contents to the actual cell when the UICollectionView creates it
 			if (_measurementCells != null)
+			{
 				_measurementCells[ItemsSource[indexPath]] = templatedCell;
+				_measurementCellGenerations[templatedCell] = _sourceGeneration;
+			}
 
 			return templatedCell;
 		}
diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
index a92f32bc46..f724bcc49a 100644
--- a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
+++ b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
@@ -256,6 +256,8 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 
 		internal void DisposeItemsSource()
 		{
+			if (CollectionView is Items.MauiCollectionView mauiCV)
+				mauiCV.NeedsCellLayout = false;
 			ItemsSource?.Dispose();
 			ItemsSource = new Items.EmptySource();
 			ReloadData();
@@ -290,6 +292,8 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 
 		public virtual void UpdateItemsSource()
 		{
+			if (CollectionView is Items.MauiCollectionView mauiCV)
+				mauiCV.NeedsCellLayout = false;
 			ItemsSource?.Dispose();
 			ItemsSource = CreateItemsViewSource();

@dotnet dotnet deleted a comment from MauiBot May 7, 2026
@kubaflo kubaflo dismissed MauiBot’s stale review May 7, 2026 10:39

Resetting for re-review

MauiBot
MauiBot previously requested changes May 7, 2026
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.

Why: try-fix-2 is the only empirically verified passing fix: it resets MeasureInvalidated on all visible cells before source swap, preventing stale cells from ever being candidates for IndexPath invalidation. It also includes proper CleanupMeasurementCells (unsubscription + Unbind) and closes the Dispose() gap that the PR leaves open. Both regression tests passed with 923 total tests.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-2`)
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
index 04d41a5607..c4c505e663 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
@@ -88,6 +88,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
 			if (disposing)
 			{
+				CleanupMeasurementCells();
 				ItemsSource?.Dispose();
 
 				((IUIViewLifeCycleEvents)CollectionView).MovedToWindow -= MovedToWindow;
@@ -139,7 +140,8 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
 			if (_isEmpty)
 			{
-				_measurementCells?.Clear();
+				ResetVisibleCellsMeasureInvalidated();
+				CleanupMeasurementCells();
 				ItemsViewLayout?.ClearCellSizeCache();
 			}
 
@@ -419,7 +421,8 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
 		public virtual void UpdateItemsSource()
 		{
-			_measurementCells?.Clear();
+			ResetVisibleCellsMeasureInvalidated();
+			CleanupMeasurementCells();
 			ItemsViewLayout?.ClearCellSizeCache();
 			ItemsSource?.Dispose();
 			ItemsSource = CreateItemsViewSource();
@@ -432,7 +435,8 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
 		internal void DisposeItemsSource()
 		{
-			_measurementCells?.Clear();
+			ResetVisibleCellsMeasureInvalidated();
+			CleanupMeasurementCells();
 			ItemsViewLayout?.ClearCellSizeCache();
 			ItemsSource?.Dispose();
 			ItemsSource = new EmptySource();
@@ -519,6 +523,34 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 			return ItemsSource.GetIndexForItem(item);
 		}
 
+		void ResetVisibleCellsMeasureInvalidated()
+		{
+			var visibleCells = CollectionView?.VisibleCells;
+			if (visibleCells is null)
+				return;
+
+			for (int i = 0; i < visibleCells.Length; i++)
+			{
+				if (visibleCells[i] is TemplatedCell cell)
+					cell.ResetMeasureInvalidated();
+			}
+		}
+
+		void CleanupMeasurementCells()
+		{
+			if (_measurementCells is null)
+				return;
+
+			foreach (var measurementCell in _measurementCells.Values)
+			{
+				measurementCell.LayoutAttributesChanged -= CellLayoutAttributesChanged;
+				measurementCell.ResetMeasureInvalidated();
+				measurementCell.Unbind();
+			}
+
+			_measurementCells.Clear();
+		}
+
 		protected object GetItemAtIndex(NSIndexPath index)
 		{
 			return ItemsSource[index];
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/TemplatedCell.cs b/src/Controls/src/Core/Handlers/Items/iOS/TemplatedCell.cs
index f0ef757432..e0ea973198 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/TemplatedCell.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/TemplatedCell.cs
@@ -57,6 +57,8 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
 		internal bool MeasureInvalidated => _measureInvalidated;
 
+		internal void ResetMeasureInvalidated() => _measureInvalidated = false;
+
 		internal IPlatformViewHandler PlatformHandler
 		{
 			get => _handler is not null && _handler.TryGetTarget(out var h) ? h : null;
diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
index a92f32bc46..5ee137a028 100644
--- a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
+++ b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
@@ -231,6 +231,19 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 			}
 		}
 
+		void ResetVisibleCellsMeasureInvalidated()
+		{
+			var visibleCells = CollectionView?.VisibleCells;
+			if (visibleCells is null)
+				return;
+
+			for (int i = 0; i < visibleCells.Length; i++)
+			{
+				if (visibleCells[i] is TemplatedCell2 cell)
+					cell.ResetMeasureInvalidated();
+			}
+		}
+
 		private void MovedToWindow(object sender, EventArgs e)
 		{
 			if (CollectionView?.Window != null)
@@ -256,6 +269,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 
 		internal void DisposeItemsSource()
 		{
+			ResetVisibleCellsMeasureInvalidated();
 			ItemsSource?.Dispose();
 			ItemsSource = new Items.EmptySource();
 			ReloadData();
@@ -290,6 +304,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 
 		public virtual void UpdateItemsSource()
 		{
+			ResetVisibleCellsMeasureInvalidated();
 			ItemsSource?.Dispose();
 			ItemsSource = CreateItemsViewSource();
 
diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/TemplatedCell2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/TemplatedCell2.cs
index b8dad3ef43..ec1a91cb16 100644
--- a/src/Controls/src/Core/Handlers/Items2/iOS/TemplatedCell2.cs
+++ b/src/Controls/src/Core/Handlers/Items2/iOS/TemplatedCell2.cs
@@ -44,6 +44,8 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 		internal bool isSupplementaryView = false;
 		internal bool MeasureInvalidated => _measureInvalidated;
 
+		internal void ResetMeasureInvalidated() => _measureInvalidated = false;
+
 		// Flags changes confined to the header/footer, preventing unnecessary recycling and revalidation of templated cells.
 		internal bool isHeaderOrFooterChanged = false;
 

@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@kubaflo kubaflo dismissed MauiBot’s stale review May 8, 2026 09:38

Resetting for re-review

MauiBot
MauiBot previously requested changes May 8, 2026
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.

Why: The PR's gate failed (per the prompt rule, failing-gate candidates must rank below passing-gate candidates). Among the four try-fix alternatives, try-fix-2 is the smallest, lowest-risk fix (≈14 production lines across both iOS handlers) and has a previously-measured passing gate run. It directly prevents the documented NSInternalInconsistencyException at its exact trigger by short-circuiting InvalidateLayoutIfItemsMeasureChanged when ItemsSource has been emptied. Caveat: try-fix-2 does NOT cover the measurement-cell race that the PR also addresses; if the PR's gate is re-run after the test reliability tweak from pr-plus-reviewer and passes, pr should win instead.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-2`)
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
index 04d41a5607..dfda1143dd 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
@@ -256,6 +256,13 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 
 		void InvalidateLayoutIfItemsMeasureChanged()
 		{
+			// When the source has no items, any visible cells with MeasureInvalidated=true belong to the
+			// previous source and are still animating out. Passing their stale index paths to
+			// InvalidateItems would throw NSInternalInconsistencyException because UIKit has already
+			// committed the reload to zero items.
+			if (ItemsSource.ItemCount == 0)
+				return;
+
 			var visibleCells = CollectionView.VisibleCells;
 			List<TemplatedCell> invalidatedCells = null;
 
diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
index 1c4f8a2b00..a3e2d51c11 100644
--- a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
+++ b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
@@ -207,6 +207,11 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 
 		void InvalidateLayoutIfItemsMeasureChanged()
 		{
+			// When the source has no items, any visible cells with MeasureInvalidated=true belong to the
+			// previous source. Skip the targeted invalidation to avoid feeding stale index paths into
+			// UIKit's batch-update bookkeeping.
+			if (ItemsSource.ItemCount == 0)
+				return;
 			var collectionView = CollectionView;
 			var visibleCells = collectionView.VisibleCells;
 			List<TemplatedCell2> invalidatedCells = null;

@MauiBot MauiBot added s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels May 8, 2026
@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@kubaflo kubaflo dismissed MauiBot’s stale review May 8, 2026 11:55

Resetting for re-review

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented May 8, 2026

🤖 AI Summary

👋 @filipnavara — new AI review results are available. Please review the latest session below.

📊 Review Session1d028df · Address PR feedback · 2026-05-08 15:37 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: IOS · Base: main · Merge base: b71adea6

🩺 Fix does not pass the tests — every test still fails after applying the fix. The PR's change does not resolve the failure(s).

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 CollectionViewTests CollectionViewTests ✅ FAIL — 5428s ❌ FAIL — 278s
🔴 Without fix — 📱 CollectionViewTests: FAIL ✅ · 5428s

(truncated to last 15,000 chars)

ble Page
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:35.5576110] 2026-05-08 07:02:35.555907-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Pushing the Same Page Disconnects Previous Toolbar Items
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:35.7123880] 2026-05-08 07:02:35.711987-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FlyoutContentSetsCorrectBottomPaddingWhenMinHeightIsSetForFlyoutHeader
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:35.7126910] 2026-05-08 07:02:35.712398-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] Microsoft.Maui.DeviceTests.ShellTests 53.5701558 ms
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:36.8394050] 2026-05-08 07:02:36.839105-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LoadedFiresOnPushedPage
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:36.9303290] 2026-05-08 07:02:36.929991-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LoadedAndUnloadedFire
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:37.5407550] 2026-05-08 07:02:37.539582-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] NavigatedToFiresAfterLoaded
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:37.8462830] 2026-05-08 07:02:37.844578-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LoadedAndUnloadedFireWhenParentRemoved
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:37.8479120] 2026-05-08 07:02:37.845487-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] Microsoft.Maui.DeviceTests.VisualElementTests+NewWindowCollection 2.1295825 ms
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:37.8900780] 2026-05-08 07:02:37.889708-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FindPlatformViewViaDefaultContainer
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:37.9395810] 2026-05-08 07:02:37.939230-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FindPlatformViewInsideView
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:37.9892570] 2026-05-08 07:02:37.988887-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FindPlatformViewInsideView
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:38.0883940] 2026-05-08 07:02:38.088081-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] GetVisualTreeElements
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:38.1401560] 2026-05-08 07:02:38.139206-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FindPlatformViewInsideLayout
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:38.2309330] 2026-05-08 07:02:38.230614-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FindVisualTreeElementWithArbitraryPlatformViewsAdded
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:38.2780640] 2026-05-08 07:02:38.277752-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FindPlatformViewInsideScrollView
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:38.3621100] 2026-05-08 07:02:38.361777-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FindFirstMauiParentElement
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:38.4471460] 2026-05-08 07:02:38.445386-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FindFirstMauiParentElement
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:38.4471940] 2026-05-08 07:02:38.446031-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] Microsoft.Maui.DeviceTests.VisualElementTreeTests 0.5905415 ms
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:40.4908540] 2026-05-08 07:02:40.489105-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] SwitchBetweenWindowShouldTriggerIsActivated
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:40.6748510] 2026-05-08 07:02:40.673701-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] MainPageSwapTests
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:40.8886050] 2026-05-08 07:02:40.888310-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] MainPageSwapTests
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.0393990] 2026-05-08 07:02:41.039045-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] MainPageSwapTests
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.2385790] 2026-05-08 07:02:41.238248-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] MainPageSwapTests
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.2889460] 2026-05-08 07:02:41.288674-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] WindowIsActivedRespondToMethodsCall
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3387540] 2026-05-08 07:02:41.338419-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Initial Dispatch from Background Thread Succeeds
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3390880] 2026-05-08 07:02:41.338856-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] Microsoft.Maui.DeviceTests.WindowTests 2.8860881 ms
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3393800] 2026-05-08 07:02:41.339195-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362]
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3394030] Test collection for Microsoft.Maui.DeviceTests.XamlTests
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3534910] 2026-05-08 07:02:41.353021-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Parsed XAML can use x:Array
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3568870] 2026-05-08 07:02:41.355618-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Parsed XAML can use x:Double
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3584440] 2026-05-08 07:02:41.356796-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Compiled XAML can use x:Array
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3592160] 2026-05-08 07:02:41.358143-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Compiled XAML can use mscorlib
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3620820] 2026-05-08 07:02:41.361739-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Parsed XAML can use mscorlib
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3625570] 2026-05-08 07:02:41.362378-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Compiled XAML can use x:Double
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3628120] 2026-05-08 07:02:41.362625-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] Microsoft.Maui.DeviceTests.XamlTests 0.0177273 ms
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3630170] 2026-05-08 07:02:41.362845-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362]
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3630310] Test collection for Microsoft.Maui.DeviceTests.LabelTests
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3756060] 2026-05-08 07:02:41.374222-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Using CharacterSpacing with LineHeight and TextDecorations works Correctly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3802820] 2026-05-08 07:02:41.376600-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Using CharacterSpacing with LineHeight and TextDecorations works Correctly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.3803140] 2026-05-08 07:02:41.380068-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Using CharacterSpacing with LineHeight and TextDecorations works Correctly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.4667750] 2026-05-08 07:02:41.465249-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Using CharacterSpacing with LineHeight and TextDecorations works Correctly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.4694640] 2026-05-08 07:02:41.469291-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FormattedStringSpanTextTransformOverridesLabelTextTransform
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.5800780] 2026-05-08 07:02:41.579824-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FontStuffAfterTextTypeIsCorrect
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.5836120] 2026-05-08 07:02:41.583369-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] VerifyLabelIsVisibleProperty
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.5853790] 2026-05-08 07:02:41.585142-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Negative MaxLines value with wrap is correct
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:41.7977460] 2026-05-08 07:02:41.797450-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:42.0097330] 2026-05-08 07:02:42.009362-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:42.2210650] 2026-05-08 07:02:42.220750-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:42.4323360] 2026-05-08 07:02:42.432063-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:42.6430830] 2026-05-08 07:02:42.642796-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:42.8536500] 2026-05-08 07:02:42.853421-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:43.0641900] 2026-05-08 07:02:43.063928-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:43.2773280] 2026-05-08 07:02:43.277001-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:43.4885030] 2026-05-08 07:02:43.488211-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:43.6992530] 2026-05-08 07:02:43.698889-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:43.9131870] 2026-05-08 07:02:43.912233-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:44.1257100] 2026-05-08 07:02:44.125422-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LabelTruncatesCorrectly
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:44.1284070] 2026-05-08 07:02:44.128139-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] VerifyLabelOpacityProperty
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:44.2335200] 2026-05-08 07:02:44.233264-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FontStuffAppliesEvenInHtmlMode
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:44.2382430] 2026-05-08 07:02:44.237996-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] TextTransformUpdated
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:44.2413740] 2026-05-08 07:02:44.240316-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] TextTransformUpdated
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:44.2428060] 2026-05-08 07:02:44.242599-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] TextTransformUpdated
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:44.2500570] 2026-05-08 07:02:44.249774-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] TextColorAppliesEvenInHtmlMode
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:44.2569640] 2026-05-08 07:02:44.256675-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LineBreakMode TailTruncation does not affect MaxLines
�[40m�[37mdbug�[39m�[22m�[49m: [07:02:44.2573090] 2026-05-08 07:02:44.257130-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[IGNORED] UpdatingFormattedTextResultsInTheSameLayout
�[40m�[37mdbug�[39m�[22m�[49m: [07:08:19.8490190] 2026-05-08 07:08:19.848547-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FormattedStringSpanTextHasCorrectColorWhenChangedAfterCreation
�[40m�[37mdbug�[39m�[22m�[49m: [07:08:29.3420880] 2026-05-08 07:08:29.341691-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FormattedStringSpanTextHasCorrectColorWhenChanges
�[40m�[37mdbug�[39m�[22m�[49m: [07:08:29.3446460] 2026-05-08 07:08:29.344383-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LineBreakMode does not affect MaxLines
�[40m�[37mdbug�[39m�[22m�[49m: [07:20:12.4395970] 2026-05-08 07:20:12.438885-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FormattedStringSpanTextHasCorrectLayoutWhenAligned
�[40m�[37mdbug�[39m�[22m�[49m: [07:32:29.4726950] 2026-05-08 07:32:29.472134-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FormattedStringSpanTextHasCorrectLayoutWhenAligned
�[40m�[37mdbug�[39m�[22m�[49m: [07:44:41.2391690] 2026-05-08 07:44:41.238548-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] FormattedStringSpanTextHasCorrectLayoutWhenAligned
�[40m�[37mdbug�[39m�[22m�[49m: [07:44:41.2420840] 2026-05-08 07:44:41.241761-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] MaxLines Initializes Correctly
�[40m�[37mdbug�[39m�[22m�[49m: [07:44:41.2438780] 2026-05-08 07:44:41.243620-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] LineBreakMode Initializes Correctly
�[40m�[37mdbug�[39m�[22m�[49m: [07:44:41.2459910] 2026-05-08 07:44:41.245750-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] InitialTextTransformApplied
�[40m�[37mdbug�[39m�[22m�[49m: [07:44:41.2477730] 2026-05-08 07:44:41.247588-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] InitialTextTransformApplied
�[40m�[37mdbug�[39m�[22m�[49m: [07:44:41.2505810] 2026-05-08 07:44:41.250399-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] InitialTextTransformApplied
�[40m�[37mdbug�[39m�[22m�[49m: [07:44:41.2529100] 2026-05-08 07:44:41.252706-0700 Microsoft.Maui.Controls.DeviceTests[11786:94362] 	[PASS] Single LineBreakMode changes MaxLines
�[40m�[37mdbug�[39m�[22m�[49m: ==================== End of ApplicationLog ====================
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[32minfo�[39m�[22m�[49m: Uninstalling the application 'com.microsoft.maui.controls.devicetests' from 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: Test log server listening on: 0.0.0.0:64561
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 0
�[40m�[32minfo�[39m�[22m�[49m: Application 'com.microsoft.maui.controls.devicetests' was uninstalled successfully
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "VGW2TD9QJ7-1",
        "exitCode": 80,
        "exitCodeName": "APP_CRASH",
        "platform": "apple",
        "device": "iPhone 11 Pro",
        "deviceOsVersion": "18.6",
        "files": [
          {
            "name": "test-ios-simulator-64_18.6-BD70EE9A-8FF8-45C3-9321-2B37A361151D.log",
            "type": "executionlog"
          },
          {
            "name": "list-ios-simulator-64_18.6-20260508_065241.log",
            "type": "devicelist"
          },
          {
            "name": "iPhone 11 Pro.log",
            "type": "systemlog"
          },
          {
            "name": "Microsoft.Maui.Controls.DeviceTests.log",
            "type": "systemlog"
          },
          {
            "name": "com.microsoft.maui.controls.devicetests.log",
            "type": "applicationlog"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
XHarness exit code: 80 (APP_CRASH)
  Passed: 0
  Failed: 0
  Tests completed with exit code: 80

🟢 With fix — 📱 CollectionViewTests: FAIL ❌ · 278s

(truncated to last 15,000 chars)

�[39m�[22m�[49m: [08:22:52.2458770] 	0x10b2c14b4 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_log_write_os_log
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2459080] 	0x10b2aba24 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : monoeg_g_logv
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2459170] 	0x10b2abb6c - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : monoeg_g_log
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2459250] 	0x10b1fe848 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : load_aot_module
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2459440] 	0x10b2d8618 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_assembly_request_load_from
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2459500] 	0x10b2d8134 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_assembly_request_open
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2459540] 	0x10b24ee4c - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_core_preload_hook
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2459590] 	0x10b2da4dc - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : invoke_assembly_preload_hook
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2459950] 	0x10b2d73f8 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_assembly_request_byname
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2460010] 	0x10b2080b0 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : load_image
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2460080] 	0x10b1fe7e0 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : load_aot_module
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2460190] 	0x10b2d8618 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_assembly_request_load_from
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2460230] 	0x10b2d8134 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_assembly_request_open
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2460270] 	0x10b24ee4c - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_core_preload_hook
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2460300] 	0x10b2da4dc - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : invoke_assembly_preload_hook
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2460420] 	0x10b2d73f8 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_assembly_request_byname
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2462620] 	0x10b2080b0 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : load_image
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2462770] 	0x10b1fe7e0 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : load_aot_module
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2462810] 	0x10b2d8618 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_assembly_request_load_from
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2463710] 	0x10b2d8134 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_assembly_request_open
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2463960] 	0x10b24ee4c - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_core_preload_hook
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464030] 	0x10b2da4dc - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : invoke_assembly_preload_hook
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464080] 	0x10b2d73f8 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_assembly_request_byname
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464230] 	0x10b2080b0 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : load_image
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464280] 	0x10b1fe7e0 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : load_aot_module
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464330] 	0x10b202fac - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_aot_get_method
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464360] 	0x10b187f3c - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_jit_compile_method_with_opt
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464460] 	0x10b185cac - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_jit_runtime_invoke
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464500] 	0x10b346910 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_runtime_invoke_checked
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464550] 	0x10b2f9818 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : create_exception_two_strings
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464630] 	0x10b2f959c - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_exception_from_name_two_strings_checked
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464680] 	0x10b2d396c - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_runtime_init_checked
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464720] 	0x10b18563c - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mini_init
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2464760] 	0x10b1dcd1c - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libmonosgen-2.0.dylib : mono_jit_init
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2488140] 	0x10ae41014 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libxamarin-dotnet.dylib : xamarin_bridge_initialize
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.2488440] 	0x10ae421e8 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/libxamarin-dotnet.dylib : xamarin_main
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3538790] 	0x1057c91f8 - /Users/cloudtest/Library/Developer/CoreSimulator/Devices/BD70EE9A-8FF8-45C3-9321-2B37A361151D/data/Containers/Bundle/Application/9588F04F-7A30-4546-B187-636986E024A6/Microsoft.Maui.Controls.DeviceTests.app/Microsoft.Maui.Controls.DeviceTests : main
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3539290] 	0x10abad3d4 - Unknown
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3539360] 	0x10a946b98 - Unknown
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3539420]
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3539470] =================================================================
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3539520] 	Basic Fault Address Reporting
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3539570] =================================================================
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3539610] Memory around native instruction pointer (0x10ad44874):0x10ad44864  c0 03 5f d6 c0 03 5f d6 10 29 80 d2 01 10 00 d4  .._..._..)......
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3539760] 0x10ad44874  e3 00 00 54 fd 7b bf a9 fd 03 00 91 16 e3 ff 97  ...T.{..........
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3539810] 0x10ad44884  bf 03 00 91 fd 7b c1 a8 c0 03 5f d6 c0 03 5f d6  .....{...._..._.
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3540000] 0x10ad44894  70 0a 80 d2 01 10 00 d4 e3 00 00 54 fd 7b bf a9  p..........T.{..
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3540050]
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3540110] =================================================================
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3540150] 	Managed Stacktrace:
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3540190] =================================================================
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3540220] 	  at <unknown> <0xffffffff>
�[40m�[37mdbug�[39m�[22m�[49m: [08:22:52.3540250] =================================================================
�[40m�[37mdbug�[39m�[22m�[49m: ==================== End of ApplicationLog ====================
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[32minfo�[39m�[22m�[49m: Uninstalling the application 'com.microsoft.maui.controls.devicetests' from 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 0
�[40m�[32minfo�[39m�[22m�[49m: Application 'com.microsoft.maui.controls.devicetests' was uninstalled successfully
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "VGW2TD9QJ7-1",
        "exitCode": 0,
        "exitCodeName": "SUCCESS",
        "platform": "apple",
        "device": "iPhone 11 Pro",
        "deviceOsVersion": "18.6",
        "files": [
          {
            "name": "test-ios-simulator-64_18.6-BD70EE9A-8FF8-45C3-9321-2B37A361151D.log",
            "type": "executionlog"
          },
          {
            "name": "list-ios-simulator-64_18.6-20260508_082247.log",
            "type": "devicelist"
          },
          {
            "name": "iPhone 11 Pro.log",
            "type": "systemlog"
          },
          {
            "name": "Microsoft.Maui.Controls.DeviceTests.log",
            "type": "systemlog"
          },
          {
            "name": "com.microsoft.maui.controls.devicetests.log",
            "type": "applicationlog"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
XHarness exit code: 0
  Passed: 922
  Failed: 3
  Tests completed successfully

⚠️ Failure Details

  • CollectionViewTests FAILED with fix (should pass)
    • Device tests: 3 of 925 failed
📁 Fix files reverted (2 files)
  • src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs

🧪 UI Tests — Category Detection

Detected UI test categories: CollectionView

🧪 UI Test Execution Results

PASSED — 1 passed, 0 failed, 0 skipped (platform: ios)

Category Result Tests Duration Notes
CollectionView ✅ PASSED 1/1 ✓ 3599.4s
Show 1 passed test name(s)

CollectionView

  • ValidateDynamicItemTemplateDisplayed ValidateDynamicEmptyViewGridDisplayed ValidateDynamicEmptyViewStringDisplayed ValidateDynamicEmptyViewTemplateDisplayed ValidateDynamicFooterGridDisplayed ValidateDynamicFooterStringDisplayed ValidateDynamicFooterTemplateDisplayed ValidateDynamicGroupFooterTemplateDisplayed ValidateDynamicGroupHeaderTemplateDisplayed ValidateDynamicHeaderGridDisplayed ValidateDynamicHeaderStringDisplayed ValidateDynamicHeaderTemplateDisplayed ValidateEmptyViewStringDisplayed ValidateCustomEmptyViewDisplayed ValidateCustomEmptyViewDisplayed_AndEmptyObservableCollectionSetFirst ValidateCustomEmptyViewDisplayed_AndEmptyViewTemplate ValidateCustomEmptyViewDisplayed_AndObservableCollectionSetFirst ValidateCustomEmptyViewDisplayed_EmptyGroupedListSetFirst ValidateCustomEmptyViewDisplayed_GroupedListSetFirst ValidateCustomEmptyViewDisplayed_WhenBasicItemTemplateSetFirst ValidateCustomEmptyViewDisplayedSetFirst_AndEmptyGroupedList ValidateCustomEmptyViewDisplayedSetFirst_AndEmptyObservableCollection ValidateCustomEmptyViewDisplayedSetFirst_AndGroupedList ValidateCustomEmptyViewDisplayedSetFirst_AndObservableCollection ValidateCustomEmptyViewDisplayedSetFirst_WhenBasicItemTemplate ValidateCustomEmptyViewSizeDisplayed ValidateCustomEmptyViewSizeDisplayed_AndCustomEmptyViewTemplateSize ValidateCustomEmptyViewSizeDisplayed_AndCustomViewHeader ValidateCustomEmptyViewSizeDisplayed_AndEmptyObservableCollectionSetFirst ValidateCustomEmptyViewSizeDisplayed_AndEmptyViewTemplate ValidateCustomEmptyViewSizeDisplayed_AndHeaderString ValidateCustomEmptyViewSizeDisplayed_AndObservableCollectionSetFirst ValidateCustomEmptyViewSizeDisplayed_EmptyGroupedListSetFirst ValidateCustomEmptyViewSizeDisplayed_GroupedListSetFirst ValidateCustomEmptyViewSizeDisplayed_WhenBasicItemTemplateSetFirst ValidateCustomEmptyViewSizeDisplayedSetFirst_AndEmptyGroupedList ValidateCustomEmptyViewSizeDisplayedSetFirst_AndEmptyObservableCollection ValidateCustomEmptyViewSizeDisplayedSetFirst_AndGroupedList ValidateCustomEmptyViewSizeDisplayedSetFirst_AndObservableCollection ValidateCustomEmptyViewSizeDisplayedSetFirst_WhenBasicItemTemplate ValidateCustomSizeEmptyViewTemplateDisplayed ValidateCustomSizeEmptyViewTemplateDisplayed_AndBasicTemplateSetFirst ValidateCustomSizeEmptyViewTemplateDisplayed_AndCustomEmptyView ValidateCustomSizeEmptyViewTemplateDisplayed_AndCustomSizeEmptyView ValidateCustomSizeEmptyViewTemplateDisplayed_AndEmptyGroupedListSetFirst ValidateCustomSizeEmptyViewTemplateDisplayed_AndEmptyViewString ValidateCustomSizeEmptyViewTemplateDisplayed_AndeObservableCollectionSetFirst ValidateCustomSizeEmptyViewTemplateDisplayed_AndGroupedListSetFirst ValidateCustomSizeEmptyViewTemplateDisplayed_EmptyObservableCollectionSetFirst ValidateCustomSizeEmptyViewTemplateDisplayedSetFirst_AndBasicItemTemplate ValidateCustomSizeEmptyViewTemplateDisplayedSetFirst_AndEmptyGroupedList ValidateCustomSizeEmptyViewTemplateDisplayedSetFirst_AndEmptyObservableCollection ValidateCustomSizeEmptyViewTemplateDisplayedSetFirst_AndGroupedList ValidateCustomSizeEmptyViewTemplateDisplayedSetFirst_AndObservableCollection ValidateEmptyViewSize ValidateEmptyViewStringDisplayed_AndBasicItemTemplateSetFirst ValidateEmptyViewStringDisplayed_AndCustomViewHeader ValidateEmptyViewStringDisplayed_AndEmptyGroupedListSetFirst ValidateEmptyViewStringDisplayed_AndEmptyViewTemplate ValidateEmptyViewStringDisplayed_AndHeaderString ValidateEmptyViewStringDisplayed_AndItemSourceEmptyObservableCollectionSetFirst ValidateEmptyViewStringDisplayed_AndObservableCollectionSetFirst ValidateEmptyViewStringDisplayed_GroupedListSetFirst ValidateEmptyViewStringDisplayedSetFirst_AndBasicItemTemplate ValidateEmptyViewStringDisplayedSetFirst_AndGroupedList ValidateEmptyViewStringDisplayedSetFirst_AndItemSourceEmptyGroupedList ValidateEmptyViewStringSetFirst_AndEmptyObservableCollection ValidateEmptyViewStringSetFirst_AndObservableCollection ValidateEmptyViewTemplateDisplayed ValidateEmptyViewTemplateDisplayed_AndBasicTemplateSetFirst ValidateEmptyViewTemplateDisplayed_AndCustomEmptyView ValidateEmptyViewTemplateDisplayed_AndEmptyGroupedListSetFirst ValidateEmptyViewTemplateDisplayed_AndEmptyViewString ValidateEmptyViewTemplateDisplayed_AndeObservableCollectionSetFirst ValidateEmptyViewTemplateDisplayed_AndGroupedListSetFirst ValidateEmptyViewTemplateDisplayed_EmptyObservableCollectionSetFirst ValidateEmptyViewTemplateDisplayedSetFirst_AndBasicItemTemplate ValidateEmptyViewTemplateDisplayedSetFirst_AndEmptyGroupedList ValidateEmptyViewTemplateDisplayedSetFirst_AndEmptyObservableCollection ValidateEmptyViewTemplateDisplayedSetFirst_AndGroupedList ValidateEmptyViewTemplateDisplayedSetFirst_AndObservableCollection VerifyCustomEmptyViewDisplaysCorrectly_WithLeftToRightFlowDirection VerifyCustomEmptyViewDisplaysCorrectly_WithRightToLeftFlowDirection VerifyCustomSizedEmptyViewDisplaysCorrectly_WithLeftToRightFlowDirection VerifyCustomSizedEmptyViewDisplaysCorrectly_WithRightToLeftFlowDirection VerifyCustomSizedEmptyViewTemplateDisplaysCorrectly_WithLeftToRightFlowDirection VerifyEmptyViewTemplateDisplaysCorrectly_WithLeftToRightFlowDirection VerifyGroupFooterTemplate_WithFooterString VerifyCanMixGroupsFalseWithCanReorderItems VerifyCanMixGroupsTrueWithCanReorderItems VerifyCanReorderItemsFalseWithCanMixGroups VerifyCanReorderItemsTrueWithCanMixGroups VerifyFlowDirectionRTLCanMixGroupsFalseWithCanReorderItems VerifyFlowDirectionRTLCanMixGroupsTrueWithCanReorderItems VerifyFlowDirectionRTLCanReorderItemsFalseWithCanMixGroups VerifyFlowDirectionRTLCanReorderItemsTrueWithCanMixGroups VerifyGroupFooterTemplate_WithBasicItemTemplate VerifyGroupFooterTemplate_WithHeaderString VerifyGroupHeaderAndFooterTemplate_WithItemSourceNull VerifyGroupHeaderAndFooterTemplate_WithObservableCollection VerifyGroupHeaderTemplate_WithBasicItemTemplate VerifyGroupHeaderTemplate_WithFooterString VerifyGroupHeaderTemplate_WithHeaderString VerifyIsGrouped_WithFooterString VerifyIsGrouped_WithHeaderString VerifyIsGroupedFalse_WithBasicItemTemplate VerifyIsGroupedFalse_WithHeaderAndFooterString VerifyIsGroupedFalse_WithItemSourceObservableCollection VerifyStringItemsObservableCollectionWhenAddItems VerifyModelItemsEmptyGroupedListWhenAddItems VerifyModelItemsEmptyGroupedListWhenRemoveItems VerifyModelItemsEmptyObservableCollectionWhenAddItems VerifyModelItemsEmptyObservableCollectionWhenRemoveItems VerifyModelItemsGroupedListWhenAddIndexAtItems VerifyModelItemsGroupedListWhenAddItems VerifyModelItemsGroupedListWhenMultipleModePreSelection VerifyModelItemsGroupedListWhenRemoveIndexAtItems VerifyModelItemsGroupedListWhenRemoveItems VerifyModelItemsGroupedListWhenSingleModePreSelection VerifyModelItemsItemsSourceNoneWhenAddItems VerifyModelItemsItemsSourceNoneWhenRemoveItems VerifyModelItemsListWhenAddItems VerifyModelItemsListWhenRemoveItems VerifyModelItemsObservableCollectionWhenAddIndexAtItems VerifyModelItemsObservableCollectionWhenAddItems VerifyModelItemsObservableCollectionWhenMultipleModePreSelection VerifyModelItemsObservableCollectionWhenRemoveIndexAtItems VerifyModelItemsObservableCollectionWhenRemoveItems VerifyModelItemsObservableCollectionWhenSingleModePreSelection VerifyStringItemsEmptyGroupedListWhenAddItems VerifyStringItemsEmptyGroupedListWhenRemoveItems VerifyStringItemsEmptyObservableCollectionWhenAddItems VerifyStringItemsEmptyObservableCollectionWhenRemoveItems VerifyStringItemsGroupedListWhenAddIndexAtItems VerifyStringItemsGroupedListWhenAddItems VerifyStringItemsGroupedListWhenMultipleModePreSelection VerifyStringItemsGroupedListWhenRemoveIndexAtItems VerifyStringItemsGroupedListWhenRemoveItems VerifyStringItemsGroupedListWhenSingleModePreSelection VerifyStringItemsItemsSourceNoneWhenAddItems VerifyStringItemsItemsSourceNoneWhenRemoveItems VerifyStringItemsListWhenAddItems VerifyStringItemsListWhenRemoveItems VerifyStringItemsObservableCollectionWhenAddIndexAtItems VerifyStringItemsObservableCollectionWhenMultipleModePreSelection VerifyStringItemsObservableCollectionWhenRemoveIndexAtItems VerifyStringItemsObservableCollectionWhenRemoveItems VerifyStringItemsObservableCollectionWhenSingleModePreSelection VerifyMeasureAllItemsWithObservableCollection VerifyDefaultScrollToRequested VerifyFlowDirectionLTRAndMeasureAllItemsWithGroupedList VerifyFlowDirectionLTRAndMeasureAllItemsWithObservableCollection VerifyFlowDirectionRTLAndKeepScrollOffsetWithGroupedList VerifyFlowDirectionRTLAndMeasureAllItemsWithGroupedList VerifyFlowDirectionRTLAndMeasureAllItemsWithObservableCollection VerifyKeepScrollOffsetWithGroupedList VerifyKeepScrollOffsetWithObservableList VerifyMeasureAllItemsWithGroupedList VerifyRemainingItemsThresholdReachedWithVerticalList VerifyReorderCompletedWithVerticalList VerifyScrolledEventWithVerticalList VerifyScrollToByIndexWithCenterPositionAndVerticalList_Carrot VerifyScrollToByIndexWithEndPositionAndVerticalList_Carrot VerifyScrollToByIndexWithMakeVisiblePositionAndVerticalList_Carrot VerifyScrollToByItemWithCenterPositionAndVerticalList_Carrot VerifyScrollToByItemWithEndPositionAndVerticalList_Carrot VerifyScrollToByItemWithMakeVisiblePositionAndVerticalList_Carrot VerifySelectionModeNoneWhenItemsSourceNone VerifySelecctionModeSingleWhenCurrentSelection VerifySelectionModeMultipleSelectionChangedEventCount VerifySelectionModeMultipleWhenCurrentSelection VerifySelectionModeMultipleWhenItemsLayoutVerticalList VerifySelectionModeMultipleWhenItemSourceObservableCollection5 VerifySelectionModeMultipleWhenItemsSourceNone VerifySelectionModeMultipleWhenPreviousSelection VerifySelectionModeMultipleWhenProgrammaticSelectionWorksWithVerticalList VerifySelectionModeMultipleWithFooterString VerifySelectionModeMultipleWithFooterTemplateView VerifySelectionModeMultipleWithFooterView VerifySelectionModeMultipleWithHeaderString VerifySelectionModeMultipleWithHeaderStringAndFooterString VerifySelectionModeMultipleWithHeaderTemplateView VerifySelectionModeMultipleWithHeaderTemplateViewAndFooterTemplateView VerifySelectionModeMultipleWithHeaderView VerifySelectionModeMultipleWithHeaderViewAndFooterView VerifySelectionModeMultipleWithToggleSelection VerifySelectionModeNoneWhenCurrentSelection VerifySelectionModeNoneWhenItemsLayoutVerticalList VerifySelectionModeNoneWhenItemsSourceObservableCollection5 VerifySelectionModeNoneWhenPreviousSelection VerifySelectionModeNoneWithFooterString VerifySelectionModeNoneWithFooterTemplateView VerifySelectionModeNoneWithFooterView VerifySelectionModeNoneWithHeaderString VerifySelectionModeNoneWithHeaderStringAndFooterString VerifySelectionModeNoneWithHeaderTemplateView VerifySelectionModeNoneWithHeaderTemplateViewAndFooterTemplateView VerifySelectionModeNoneWithHeaderView VerifySelectionModeNoneWithHeaderViewAndFooterView VerifySelectionModeSingleSelectionChangedEventCount VerifySelectionModeSingleWhenItemsLayoutVerticalList VerifySelectionModeSingleWhenItemsSourceNone VerifySelectionModeSingleWhenItemsSourceObservableCollection5 VerifySelectionModeSingleWhenPreviousSelection VerifySelectionModeSingleWhenProgrammaticSelectionWorksWithVerticalList VerifySelectionModeSingleWithFooterString VerifySelectionModeSingleWithFooterTemplateView VerifySelectionModeSingleWithFooterView VerifySelectionModeSingleWithHeaderString VerifySelectionModeSingleWithHeaderStringAndFooterString VerifySelectionModeSingleWithHeaderTemplateView VerifySelectionModeSingleWithHeaderTemplateViewAndFooterTemplateView VerifySelectionModeSingleWithHeaderView VerifySelectionModeSingleWithHeaderViewAndFooterView ItemsFromViewModelShouldBeSelected EmptyViewItemsSourceNullStringWorks EmptyViewItemsSourceNullViewWorks GroupingAndHeaderWorks HeaderFooterGridWorks HeaderFooterHorizontalViewWorks HeaderFooterStringWorks HeaderFooterTemplateWorks HeaderFooterViewWorks SelectionShouldUpdateBinding Bugzilla21177Test ClearingGroupedNoCrash NoBindingErrors DynamicallyLoadCollectionView CollectionViewItemsSourceTypesDisplayAndDontCrash KeepItemsInView AddingGroupToUnviewedGroupedCollectionViewShouldNotCrash AddingItemToUnviewedCollectionViewShouldNotCrash EmptyViewShouldNotCrash CollectionViewInfiniteScroll CollectionShouldInvalidateOnVisibilityChange ChildAddedShouldFire CollectionViewHeaderShouldNotScroll CollectionViewWithFooterShouldNotNSRangeExceptionCrashOnDisplay JustWhalingAwayOnTheCollectionViewWithAddsAndClearsShouldNotCrash CollectionviewFooterHideswhenDynamicallyAddorRemoveItems CollectionViewHeaderBlankWhenLastItemRemoved DragItemIntoEmptyGroupShouldSucceed EmptyGroupCreationShouldWork Issue12374Test InitiallyInvisbleCollectionViewSurvivesiOSLayoutNonsense CollectionViewShouldSourceShouldResetWhileInvisible CollectionShouldInvalidateOnVisibilityChange CollectionViewWithFooterShouldNotCrashOnDisplay HeaderAndFooterRender SwipeViewInCollectionViewDoesNotTriggerSelection LastItemIsVisilbe Issue16320Test CollectionViewBindingContextOnlyChangesOnce Issue17400Test VerifyGroupedCollectionViewVisibleItemIndices ReorderingItemToEndWithHeaderDoesNotCrash ReorderingLastItemWithHeaderDoesNotCrash CollectionViewShouldNotCrashWhenIsGroupedChangesInBothDirections Issue17865Test CollectionViewAddGroupWhenViewIsEmpty CollectionViewDuplicateViewsWhenAddItemToGroup VerifyScrollToIndexWithHeader RemoveEmptyViewAtRuntime CollectionViewGroupFooterTemplateShouldNotCrash Issue18751Test HeaderAndFooterShouldBeVisible UpdateCollectionViewHeaderTest CanTapButtonOnEmptyView CollectionViewIsEnabledFalsePreventsInteractions NoNREWhenChangingGridItemsLayout CollectionViewSelectionChangesVisualState ScrollToEndDoesntCrash RefreshShouldNotChangeSize VerifyCollectionViewItemsAfterScrolling GroupedCollectionViewItems CollectionViewSingleItemAlignmentWithFooter CollectionViewFirstItemCorrectlySetsTheMeasure CollectionViewItemsResizeWhenContraintsOnCollectionViewChange CollectionViewWorksWhenRotatingDevice VerifyCollectionViewVisualState SelectionShouldNotMovedToTopWithGroupedCollection CollectionViewSelectedItemBackgroundShouldPersistAfterModalNavigation RemoveItemWhenSelectionChanged ScrollToOnUnloadedCollectionViewShouldNotCrash CollectionViewItemsShouldResizeWhenWidthDecreases CollectionViewWithHeaderAndRefreshViewShouldNotCrashOnPullToRefresh SingleItemAlignmentInCollectionViewHorizontalGridLayout CollectionViewMeasureFirstItem CollectionViewEmptyViewDefaultTemplateShouldNotCrashOnDisplay CollectionViewEmptyViewOtherTemplateShouldNotCrashOnDisplay HeaderShouldNotCollapseWithItems CollectionViewHorizontalItemSpacing AppShouldNotCrashAfterLoadingGroupedCollectionView CollectionViewPreSelectionShouldUpdate Issue25649Test LayoutPassesShouldNotIncrease CollectionViewDynamicHeaderShouldNotCrashOnDisplay CVHorizontalLinearItemsLayoutItemSpacing RemainingItemsThresholdReachedCommandFired CollectionViewShouldUseFallBackItemsLayout CollectionViewWithFallbackVauleShouldUpdateAtRunTime CollectionView2ShouldFindAncestorType SelectedItemVisualIsCleared AccessibilityTraitsSetCorrectly AccessibilityTraitsSetCorrectly CollectionViewShouldNotCrash ScrollToIndexZeroShowsFirstItemNotHeader CollectionViewHeaderSizewithIsVisibleBinding RightToLeftFlowDirectionShouldWork ShouldIgnoreBottomContentInsetForCollectionViewItems Issue2777Test AppShouldNotCrashWhenModifyingCollectionView CollectionViewShouldHaveCorrectHeight EmptyViewHeaderFooterToggleNullToNonNull EmptyViewHeaderFooterTemplateToggleNullToNonNull ItemsViewHeaderFooterToggleNullToNonNull ItemsViewHeaderFooterTemplatedToggleNullToNonNull FooterTemplateShouldNotScrollWhenEmptyViewIsDisplayed SwitchingVisibilityAndChangingItemsSourceShouldNotCrash Issue28212_CollectionView UpdateGroupHeaderAndFooterDynamically UpdateHeaderFooterTemplateDynamically UpdateHeaderFooterDynamically ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse FooterWithEmptyCVShouldHaveCorrectSize FooterShouldDisplayAtBottomOfEmptyView CollectionViewShouldChangeItemsLayout CellLayoutUpdatesCorrectlyAfterDeviceOrientationChanges HeaderFooterSelectionAfterItemsLayoutChangeShouldNotCrash KeepLastItemInViewShouldWork EmptyViewStringWithHeaderAndFooterAsView EmptyViewStringWithHeaderAndFooterString ItemSizeShouldRespondForItemSizingStrategy ItemTemplateShouldUpdateDynamicallyWithCacheClear VerifyCVGroupHFTemplateWithObservableCollection VerifyCVGroupHFTemplateWithStringCollection VerifyCVNoSectionCrashOnAddFlatItem VerifyDataTemplateParentIsNotNull VerifyGridItemsLayoutLeftAlignsSingleItem VerifyEmptyViewResizesWhenBoundsChange SettingItemSourceToNullShouldNotCrash SettingSelectedItemAndItemSourceShouldNotCrash CollectionViewSelectionShouldClear CollectionViewSelectionModeOnDarkTheme CollectionViewSelectionModeOnLightTheme EnsureCollectionViewLayoutOnItemsSourceChange ChangingIsGroupedOnRuntime CustomCollectionViewShouldScroll VerifyCollectionViewEmptyView VerifyNoExceptionOnInvalidGroupIndex UpdateItemScrollModeDynamically EnsureCollectionViewLayoutOnItemsSourceChange RemoveHeaderFooterAtRuntime AddHeaderFooterAtRuntime HeaderShouldBeRemovedWhenSetToNull FooterShouldBeRemovedWhenSetToNull CanReorderWithItemDataTemplateSelector CollectionViewDisconnectsHandlersAfterNavigationBack VerifyCVBackgroundAndBackgroundColorWithVSM VerticalGridCollectionViewLTRToRTLToggleShouldWork VerticalGridCollectionViewRTLColumnMirroringShouldWork FlowdirectionShouldWorkForHeaderFooter EmptyViewShouldDisplayWhenCollectionViewIsInsideVerticalStackLayout BottomSheetDetentHeightIsCorrectWhenCollectionViewIsMeasuredBeforeMount GroupHeaderSizeShouldNotChangeWithItemSizingStrategy EmptyViewShouldRemeasureWhenParentLayoutChanges CollectionViewScrolledEventShouldNotFireOnInitialLoad CollectionViewItemsShouldRespectSafeAreaEdges FirstVisibleItemIndexShouldBeCorrectAfterScrollTo ProgrammaticScrollToWorksWithHorizontalLayout EmptyViewSwapShouldNotRevealFilteredOutItems SelectingItemInCollectionViewWithHeaderTemplateDoesNotCrash CollectionViewHeightIsCorrectAfterDelayedLoad VerticalItemsRemainFullyVisibleAfterChangingSpacing CollectionViewScrollsWhenRefreshViewDisabled InitiallyEmptySourceDisplaysAddedItem SwappingEmptyViews FlexLayoutsInFramesShouldSizeCorrectly CollectionViewResetTest DoNotAccessDisposedCollectionView LastColumnShouldBeVisible SelectionChangedCommandParameterBoundToSelectedItemShouldMatchSelectedItem EmptyViewBecomesVisibleWhenItemsSourceIsCleared EmptyViewHidesWhenItemsSourceIsFilled SelectedItemsNotInSourceDoesNotCrash HeaderDoesNotBreakIndexes Issue7338Test AddingItemsToGroupedCollectionViewShouldNotCrash MeasureFirstItemMakesItemsDisappearOnAndroid SelectionChangedShouldBeRaisedOnceWhenSelectionChanges NavigatingBackToCollectionViewShouldNotCrash CheckEmptyViewMargin ReappearingCollectionViewShouldNotThrowNRE CollectionViewHeaderTemplateAndFooterTemplateDontWork CollectionViewInShellShouldBeVisible RotatingCollectionViewWithHTMLShouldNotHangOrCrash ClearingGroupedCollectionViewShouldNotCrash BindablePropertiesAvailableAtOnElementChanged EmptyViewShouldNotCrash AllEmptyGroupsShouldNotCrashOnItemInsert AddRemoveEmptyGroupsShouldNotCrashOnInsert Issue3884Test NestedCollectionViewsShouldNotCrash InvalidScrollToIndexShouldNotCrash CanScrollToGroupAndItemIndex CanScrollToGroupAndItem RemainingItemsThresholdReachedEventShouldTrigger VisitAndUpdateItemsSource("Observable Collection","Add/RemoveItemsList",19,6) VisitAndUpdateItemsSource("Observable Collection","Add/RemoveItemsGrid",19,6) VisitAndUpdateItemsSource("Default Text","VerticalListCode",101,11) VisitAndUpdateItemsSource("Default Text","HorizontalListCode",101,11) VisitAndUpdateItemsSource("Default Text","VerticalGridCode",101,11) VisitAndUpdateItemsSource("Default Text","HorizontalGridCode",101,11) VisitAndUpdateItemsSource("DataTemplate","VerticalListCode",19,6) VisitAndUpdateItemsSource("DataTemplate","HorizontalListCode",19,6) VisitAndUpdateItemsSource("DataTemplate","VerticalGridCode",19,6) VisitAndUpdateItemsSource("DataTemplate","HorizontalGridCode",19,6) (5 s 4 s 4 s 4 s 4 s 4 s 4 s 6 s 6 s 4 s 4 s 4 s 3 s 2 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 2 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 2 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 2 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 2 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 3 s 4 s 3 s 3 s 3 s 3 s 3 s 4 s 6 s 7 s 7 s 7 s 7 s 8 s 7 s 8 s 3 s 3 s 4 s 3 s 3 s 3 s 3 s 3 s 3 s 2 s 3 s 2 s 4 s 3 s 3 s 3 s 3 s 6 s 4 s 8 s 7 s 4 s 8 s 3 s 3 s 3 s 4 s 6 s 3 s 7 s 6 s 3 s 7 s 3 s 3 s 3 s 3 s 6 s 4 s 7 s 6 s 4 s 7 s 2 s 2 s 3 s 3 s 9 s 7 s 6 s 3 s 7 s 5 s 7 s 5 s 5 s 13 s 6 s 4 s 15 s 9 s 4 s 6 s 8 s 5 s 10 s 9 s 10 s 9 s 9 s 8 s 4 s 3 s 5 s 3 s 5 s 4 s 3 s 4 s 3 s 4 s 5 s 4 s 4 s 5 s 5 s 6 s 4 s 5 s 5 s 3 s 3 s 3 s 4 s 4 s 4 s 4 s 4 s 4 s 4 s 6 s 4 s 4 s 4 s 4 s 3 s 3 s 4 s 3 s 4 s 5 s 4 s 4 s 5 s 5 s 6 s 4 s 5 s 6 s 14 s 14 s 15 s 15 s 24 s 14 s 14 s 25 s 3 s < 1 ms 959 ms 790 ms 1 s 59 ms 28 s 10 s 10 s 99 ms 8 s 75 ms 434 ms 1 s 69 ms 1 s 2 s 3 s 3 s 1 s 2 s 840 ms 1 s 84 ms 712 ms 270 ms 1 s 124 ms 878 ms 109 ms 1 s 935 ms 2 s 2 s 1 s 1 s 1 s 1 s 3 s 925 ms 321 ms 421 ms 333 ms 1 s 601 ms 2 s 845 ms 1 s 13 s 6 s 3 s 319 ms 315 ms 183 ms 1 s 2 s 1 s 1 s 2 s 1 s 891 ms 972 ms 20 s 248 ms 367 ms 5 s 4 s 2 s 263 ms 778 ms 848 ms 2 s 17 s 1 s 352 ms 5 s 138 ms 1 s 1 s 2 s 4 s 4 s 91 ms 3 s 382 ms 879 ms 1 s 131 ms 3 s 92 ms 4 s 3 s 4 s 3 s 87 ms 802 ms 3 s 3 s 3 s 2 s 3 s 292 ms 493 ms 1 s 1 s 2 s 1 s 108 ms 102 ms 1 s 2 s 1 s 1 s 2 s 1 s 920 ms 550 ms 691 ms 878 ms 1 s 1 s 846 ms 917 ms 1 s 2 s 757 ms 1 s 1 s 4 s 1 s 1 s 1 s 1 s 2 s 3 s 2 s 4 s 1 s 1 s 159 ms 1 s 2 s 1 s 488 ms 1 s 1 s 1 s 7 s 730 ms 1 s 1 s 4 s 1 s 6 s 191 ms 1 s 1 s 124 ms 785 ms 1 s 1 s 573 ms 6 s 88 ms 2 s 369 ms 1 s 3 s 344 ms 6 s 3 s 144 ms 23 s 983 ms 124 ms 105 ms 996 ms 1 s 4 s 670 ms 12 s 11 s 7 s 12 s 25 s 26 s 25 s 24 s 30 s 25 s 24 s 24 s 26 s 25 s)

Failures here are informational only — they do not block the gate or affect try-fix candidate scoring.


🔍 Regression Cross-Reference

🔍 Regression Cross-Reference

🟢 No regression risks detected. No labeled bug-fix PRs in the last 6 months touched the modified files.


🔍 Pre-Flight — Context & Validation

Pre-Flight — PR #35245

PR Summary

Title: Fix iOS CollectionView stale layout invalidations
Author: filipnavara (member)
Issue: #35244 (verified, milestone .NET 10.0 SR8)
Base: dotnet/maui:main · Head: filipnavara/maui:cv-invalid-measurements
Files changed: 3 (113 +, 14 −)
Platform: iOS / MacCatalyst
Labels: s/agent-reviewed, s/agent-fix-win, platform/ios, area-controls-collectionview

Bug

Two related races in iOS ItemsViewController (and ItemsViewController2) on data-source changes during pending UIKit layout/animation:

  1. Stale NSIndexPath in InvalidateLayoutIfItemsMeasureChanged. Visible cells with MeasureInvalidated == true are collected as TemplatedCell references; their index paths are resolved later via CollectionView.IndexPathForCell. If ItemsSource changes between collection and resolution, the path can refer to the previous source state and be invalid for the current one. UIKit then throws:
    NSInternalInconsistencyException: attempting to invalidate an item at an invalid indexPath … numItems: 0.

  2. Detached measurement cells stay subscribed. _measurementCells.Clear() only drops the dictionary references; cached TemplatedCells remain bound (BindingContext set) and subscribed to LayoutAttributesChanged. After source replace/empty/dispose, binding-driven measure invalidations from those orphaned cells can flow back through CellLayoutAttributesChanged → handler, contributing to layout work on stale state.

Repro requires: pending animation from observable mutation + source replace/clear before UIKit completes its next layout pass + visible cells with MeasureInvalidated == true.

Fix (as submitted)

In ItemsViewController.cs and ItemsViewController2.cs (Items2):

  • InvalidateLayoutIfItemsMeasureChanged(): resolve NSIndexPath immediately for each invalidated visible cell and validate against the current ItemsSource (IsIndexPathValid / IndexPathHelpers.IsIndexPathValid); only valid paths are passed to UICollectionViewFlowLayoutInvalidationContext.InvalidateItems.
  • New ClearMeasurementCells() helper in ItemsViewController.cs — unsubscribes LayoutAttributesChanged and calls Unbind() for each cached cell before clearing the dictionary. Used at all three clear sites (CheckForEmptySource, UpdateItemsSource, DisposeItemsSource).
    • Note: Items2/ItemsViewController2.cs does NOT receive an equivalent helper in this PR — only the index-path validation change.

Regression Test

src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs adds two [Fact]s (one for CollectionViewHandler, one for CollectionViewHandler2) that:

  1. Build a CollectionView with observable items + label-binding template.
  2. Mutate every visible label's text → forces MeasureInvalidated.
  3. Append an item to the observable source → starts an animation.
  4. Set ItemsSource = null immediately afterwards.
  5. Force PerformBatchUpdatesAsync(()=>{}) + LayoutIfNeeded and assert "no crash".

Gate Result (provided)

Gate FAILED — both runs reported FAIL:

  • Without-fix run: FAIL (5428s)
  • With-fix run: FAIL (278s)

Means: on this iOS hardware/sim run, the new regression test still fails after the fix is applied, OR the harness tagged the run as failed for an unrelated reason. Either way, gate does not certify the fix end-to-end on iOS device tests. Will be discussed in the report.

Files Classified

File Class Notes
src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs Source (Items v1, iOS/MacCat) Index-path validation + ClearMeasurementCells
src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs Source (Items2, iOS/MacCat) Index-path validation only — no measurement-cell helper
src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs DeviceTests Two new [Fact]s + helper

Code-Review Verdict (summary)

See pre-flight/code-review.md for the full reviewer output. Key concerns identified:

  • ⚠️ Asymmetry between ItemsViewController and ItemsViewController2. The PR centralizes measurement-cell teardown only in v1; v2 still uses raw _measurementCells.Clear() (no unsubscribe / no Unbind). The exact same race the issue describes is reachable in Items2. Either v2 should get the same ClearMeasurementCells() helper, or the PR description should explain why it's not needed there.
  • ⚠️ Possible NRE in ClearMeasurementCells. _measurementCells is iterated unconditionally but historically dereferenced as _measurementCells?.Clear(). If the field can be null at any of the three call sites, the new helper will throw. Verify the field is non-null at construction; if so, consider tightening the original null-conditional sites for consistency.
  • ⚠️ Test forces synchronous batch flush via private API. PerformBatchUpdatesAsync(() => {}) is fine but the test relies on tight ordering of UIKit animations; potential to be flaky on slower simulators. Consider asserting via Record.Exception to make the "no crash" intent explicit.
  • ✅ Index-path validation logic is correct and matches the issue's analysis. Using IsIndexPathValid against the current ItemsSource is the right gate.

Verdict: NEEDS_DISCUSSION (medium confidence) — the v1/v2 asymmetry is the main outstanding question.


🔬 Code Review — Deep Analysis

Code Review — PR #35245

Title: Fix iOS CollectionView stale layout invalidations
Issue: #35244
Files changed: 3 (2 product, 1 test)

Verdict: LGTM (with minor suggestions)

Confidence: High

The fix is small, targeted, and correctly addresses a real class of crash on iOS CollectionView when an items-source reset interleaves with an in-flight cell measurement invalidation. The accompanying device test exercises both the v1 (Items/iOS) and v2 (Items2/iOS) handler paths.


Blast Radius

  • Surfaces touched: InvalidateLayoutIfItemsMeasureChanged on both iOS handler controllers, plus a new private ClearMeasurementCells helper in v1 only.
  • Public API: none. ClearMeasurementCells is private; helpers used (IsIndexPathValid, IndexPathHelpers) already exist.
  • Platforms affected: iOS / MacCatalyst only. Android, Windows, Tizen unchanged.
  • CollectionView routing: Per the dimension routing table this is the active iOS code (Items/ deprecated for iOS new work, but bugfix maintenance there is allowed; Items2/ is the active iOS handler). Both are touched, which is correct.
  • Performance: still O(visibleCells). The change actually reduces allocations vs. the old code by replacing a deferred Select(...).ToArray() with an eagerly-built List<NSIndexPath>, and avoids the previous bug where IndexPathForCell returning null would have produced a null entry in the array.

Failure-mode probes

Probe Outcome
IndexPathForCell returns null for a recycled cell New code skips it (indexPath is not null). Old code passed it through to InvalidateItems → array contained null.
ItemsSource swapped to empty before invalidation runs New code rejects stale paths via IsIndexPathValid.
Animation in flight + ItemsSource = null Reproduced by the new device test on both v1 and v2 handlers.
ClearMeasurementCells re-entrancy via Unbind() setting BindingContext = null Unbind() only sets the BC of the inner virtual view; cannot mutate _measurementCells. Safe.
_measurementCells field nulled by future code New helper would NRE; previous code used ?.Clear(). Field is currently initialized inline and never nulled — see suggestion below.
Items2 has its own measurement-cell cache Searched: _measurementCells only exists in v1. Items2 measures via PreferredLayoutAttributesFittingAttributes directly. So no v2 counterpart needed for the cleanup half of the fix.
Hot-path regression for-loop preserved, new IsIndexPathValid is a pair of integer comparisons. No allocation, no LINQ. ✔
Test: WaitForUIUpdate(frame, ...) with default-valued frame Standard pattern in this file (see CollectionViewCacheTests.ios.cs:122). ✔
PerformBatchUpdatesAsync(() => {}) to flush a pending reload Standard iOS device-test idiom. ✔

Errors

None.

Warnings

None blocking. The ClearMeasurementCells cleanup correctly addresses both the leak path (subscriptions to CellLayoutAttributesChanged lingering on prototype cells whose backing data is gone) and the stale-callback path (preventing a measurement-cell event from firing into a controller whose ItemsSource has moved on). This addresses Dimension 11 (Memory Leak Prevention) — event handlers unsubscribed in cleanup, BindingContext nulled — as well as Dimension 19 (CollectionView iOS Items2/) and the deprecated-iOS leg of Dimension 19.

Suggestions

  1. Items2 parity comment — Items2's InvalidateLayoutIfItemsMeasureChanged got the eager-validate half of the fix, but not the ClearMeasurementCells half because Items2 doesn't cache measurement cells. A one-line comment near the v2 method pointing back to the v1 cleanup rationale would protect future changes that re-introduce a measurement-cell cache.

  2. Cleanup contractClearMeasurementCells deliberately does not Dispose the cached TemplatedCell natives (correct: ARC owns them, and the cells may still be in UICollectionView's reuse pool). A brief XML or // comment explaining "we unbind to break .NET refs but leave native disposal to ARC" prevents a future reviewer from "fixing" this into a leak-bug-by-Dispose.

  3. Test post-condition — The test asserts no crash but doesn't assert any post-condition. A cheap Assert.Equal(0, uiCollectionView.NumberOfItemsInSection(0)) after the final LayoutIfNeeded would also catch a future regression where the reset is silently dropped.

  4. Defensive consistency — Surrounding code still has if (_measurementCells != null) guards (e.g. line 505, 919). The new helper drops the guard. Either keep them all or drop them all; today the field is only initialized inline, so either direction is fine, but mixing is mildly confusing.

Regression risks tracked

None of the deletions correspond to a previously-merged bug fix's added lines (the deleted lines built the List<TemplatedCell> — not associated with a known fix PR). No regression-check/risks.json REVERT entries were involved.

Test coverage assessment (Dimension 6)

  • Bug fix includes a regression test reproducing the original repro (ItemsSource = null mid-animation with measure-invalidated cells). ✔
  • Test covers both v1 (CollectionViewHandler) and v2 (CollectionViewHandler2) — important since the bug existed in both paths. ✔
  • Test is iOS-only (.iOS.cs), which is correct since the bug is iOS-specific. ✔
  • Uses deterministic PerformBatchUpdatesAsync + LayoutIfNeeded instead of Task.Delay. ✔

Top 3 actionable concerns

  1. Add a comment in ItemsViewController2.cs explaining the absence of a ClearMeasurementCells analogue (no v2 cache today, but mark the rationale).
  2. Strengthen the regression test with at least one positive post-condition assertion (e.g. NumberOfItemsInSection(0) == 0) so future regressions that silently swallow the reset are caught.
  3. Resolve the inconsistency between ClearMeasurementCells (no null check on _measurementCells) and the surrounding defensive _measurementCells != null checks at lines 505 and 919.

🔧 Fix — Analysis & Comparison

Try-Fix Aggregate — PR #35245

Four independent fix candidates were generated against origin/main, each loaded with a different MAUI expert dimension:

# Dimension Approach LOC vs PR Items2 parity Covers measurement-cell bug Test
1 handler-lifecycle Source-generation token; bail to InvalidateLayout() if generation changed similar ⏭ skipped
2 collectionview-ios ClearMeasureInvalidation on visible cells at source-replace time + ClearMeasurementCells for v1 + DisposeItemsSource symmetry for v2 larger ⏭ skipped
3 threading-async try/catch (NSInternalInconsistencyException) → fall back to full invalidate smaller ⏭ skipped
4 minimal-fix Early-exit when ItemsSource is EmptySource or ItemCount == 0 smallest ⏭ skipped
PR per-path validation + unbind Validate each NSIndexPath against current source; unsubscribe + Unbind() cached measurement cells baseline partial (path validation only) gate FAIL

Test environment constraint (documented)

Gate ran the iOS DeviceTests suite twice (without-fix and with-fix) and consumed ~5,400 + 278 seconds. With-fix returned FAIL — meaning the harness did not certify the official PR fix on this rig either. Sequential 4× re-runs of an already-questionable test pipeline within the autonomous review window would burn ~5–6 hours and not produce trustworthy distinguishing signals between candidates. All four candidates are therefore documented based on structural correctness review and diff inspection, not full device tests.

Cross-pollination round

After Round 1, asking each model for new ideas:

  • claude-opus-4.6 (try-fix-1): "NEW IDEA: combine generation-guard with PR's per-path validation as belt-and-braces" → effectively reverts to PR + small addition; not pursued because the PR's per-path validation is already the more precise guard.
  • claude-sonnet-4.6 (try-fix-2): "NO NEW IDEAS" — adding ClearMeasurementCells already mirrored the PR's measurement-cell concern.
  • gpt-5.3-codex (try-fix-3): "NEW IDEA: drop try/catch; instead detect via ItemsSource.ItemCount mismatch" → converges on try-fix-4. Already covered.
  • gemini-3-pro-preview (try-fix-4): "NO NEW IDEAS".

Exhausted: Yes (3 of 4 returned NO NEW IDEAS or converged on existing candidates).

Comparison vs PR

The PR's fix is the only candidate that addresses both bugs the issue describes (stale paths AND orphaned measurement-cell subscriptions) in a precise, targeted way:

Selected fix: PR's fix (potentially augmented with pr-plus-reviewer's reviewer comments + the test post-condition).


📋 Report — Final Recommendation

Comparative Analysis — PR #35245

Candidate Slate

ID Source Files touched Bug 1 (stale paths) Bug 2 (orphan subs) Items2 parity Risk
pr submitted PR 3 ✅ per-path IsIndexPathValid ✅ unsubscribe + Unbind in v1 partial (path validation only) Low
pr-plus-reviewer PR + reviewer feedback 3 ✅ same ✅ same partial + comment Low
try-fix-1 generation token 2 ✅ via coarse fallback ✅ symmetric in v1+v2 Low
try-fix-2 flag-clear + symmetric ClearMeasurementCells 4 ✅ (different mechanism) ✅ symmetric Med
try-fix-3 catch NSInternalInconsistency 2 ✅ reactive ✅ symmetric Med (anti-pattern)
try-fix-4 empty-source bail-out 2 ⚠️ partial — only numItems == 0 ✅ symmetric Low (incomplete)

Test gate signal

❌ Gate FAILED for the PR fix on this iOS hardware. With-fix run still reports FAIL after 278s. Without-fix run reported FAIL after 5,428s (likely the new regression test failed).

This means: none of the candidates were empirically verified by the test harness in this session. Per the user's rule "candidates that failed regression tests MUST be ranked lower than candidates that passed them" — all candidates are equally affected: all share the same gate-failure baseline (none was re-tested), so the rule does not differentiate them. Selection therefore turns on structural quality.

Ranking (highest first)

  1. pr-plus-reviewer — Adds three additive doc comments + one stricter test assertion on top of pr. Addresses every actionable item the maui-expert-reviewer surfaced. Strict superset of pr.
  2. pr — The PR as submitted. Addresses both bugs precisely; smallest surface area among the dual-bug candidates.
  3. try-fix-2 — Also addresses both bugs and reaches Items2 parity for the orphan-subs concern. Loses on touch-surface (introduces new internal TemplatedCell.ClearMeasureInvalidation/TemplatedCell2.ClearMeasureInvalidation methods) and on subtlety (clearing the flag is a state mutation that's harder to reason about than the PR's pure path validation).
  4. try-fix-1 — Symmetric and small, but misses the orphan-subs bug entirely. Coarser fallback.
  5. try-fix-3 — Catches NSInternalInconsistencyException — recovery from UIKit invariant-violation is officially undefined. Misses orphan-subs.
  6. try-fix-4 — Catches the reported stack trace (numItems==0) but not the general non-empty-shorter-source case. Misses orphan-subs. Incomplete.

Recommendation

Winner: pr-plus-reviewer.

Rationale: It is the PR's fix with three additive, low-risk improvements that address every actionable item the expert reviewer surfaced — (a) null-check parity inside ClearMeasurementCells, (b) a forward-looking comment in ItemsViewController2.cs documenting the v1/v2 measurement-cell asymmetry, and (c) a positive NumberOfItemsInSection(0) == 0 post-condition in the new regression test that catches a class of "silent reset" regressions the original test cannot. None of these changes alters runtime behaviour; the underlying fix mechanism (per-path IsIndexPathValid + ClearMeasurementCells unsubscribe + Unbind) is the only candidate that addresses both bugs the issue documents, with the smallest surface area.

Recommendation to PR author: apply the three reviewer-suggested edits and merge. The gate failure on this rig is not actionable from the candidate-comparison angle (it equally affects every candidate) and should be triaged as a separate test-environment concern — consider whether the new regression test is reliably reproducing the race on this simulator before relying on it as a gate.


@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels May 8, 2026
@kubaflo kubaflo changed the base branch from main to inflight/current May 8, 2026 17:40
@kubaflo kubaflo merged commit 2950e88 into dotnet:inflight/current May 8, 2026
7 of 8 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR7 milestone May 8, 2026
@kubaflo kubaflo added s/agent-gate-failed AI could not verify tests catch the bug s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-review-incomplete labels May 20, 2026
PureWeen pushed a commit that referenced this pull request Jun 2, 2026
### Description of Change

There are two related races in the iOS CollectionView handler when the
item source changes while cells are still participating in UIKit layout.

The first race happens after a visible templated cell invalidates its
measure. MAUI records that state on the cell and the next
ViewWillLayoutSubviews pass asks UICollectionViewFlowLayout to
invalidate exactly those changed items. Previously the handler collected
the cells first and converted them back to index paths later, at the
point where the invalidation context was created. If the bound
ItemsSource had changed in the meantime, for example an observable
source inserted items and the view then cleared or replaced ItemsSource
before the next layout pass, UIKit could still report a visible cell
whose current index path no longer existed in MAUI's new ItemsSource.
Passing that stale index path to InvalidateItems leaves UICollectionView
and the data source with inconsistent item counts and can crash during
layout.

Fix that path by resolving each invalidated visible cell to an
NSIndexPath immediately and keeping only paths that are still valid for
the current ItemsSource. The invalidation context is then built from the
validated paths. This preserves targeted invalidation for normal measure
changes, while dropping cells that belong to the previous source state
and cannot be safely invalidated by item path anymore.

The second race involves measurement cells. ItemsViewController keeps
prototype templated cells in _measurementCells so their realized content
can be transferred to real UICollectionView cells. When an ItemsSource
update, empty-source transition, or source disposal clears that
dictionary, the old code only removed the references. Those measurement
cells could still be bound to item view models and still subscribed to
LayoutAttributesChanged. Later binding or property changes from that
stale content could propagate measure/layout invalidations through cells
that are no longer owned by the active source state. In the worst case
this combines with UIKit's pending layout work after ReloadData or
source clearing and contributes to the same stale layout invalidation
problem; it can also keep disconnected measurement content behaving as
if it were still live.

Fix that by centralizing measurement-cell clearing. Before the cache is
cleared, each cached cell is detached from the layout-attribute event
and unbound so its BindingContext is removed and future measure
invalidations from that stale measured content do not flow back into the
CollectionView layout.

The regression test reproduces the important ordering: a templated
CollectionView is displayed, a visible label changes text so the cell
measure is invalidated, the observable source mutates, and ItemsSource
is immediately cleared before UIKit finishes its next layout pass. The
test forces layout afterward and verifies this no longer crashes.

### Issues Fixed

Fixes #35244

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-failed AI could not verify tests catch the bug s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] CollectionView tries to invalidate cells with invalid indexes

4 participants