From 1db8c3b69e4a64f3baf970c8caed5592ea55f7f1 Mon Sep 17 00:00:00 2001 From: baaaaif Date: Fri, 27 Mar 2026 08:07:02 +0100 Subject: [PATCH 1/6] fix: prevent ArgumentOutOfRangeException in ObservableGroupedSource when removing a group on iOS Skip the pre-processing UpdateSection call for Remove actions. Standard INCC semantics require the item to already be removed from the backing collection before CollectionChanged fires. This means _groupSource has N-1 items when MAUI receives CollectionChanged(Remove), but UIKit still has N sections. Calling UpdateSection at this point iterates all N of UIKit's sections and calls GetGroupCount(N-1) -> _groupSource[N-1] -> ArgumentOutOfRangeException. The post-processing UpdateSection call (after DeleteSections) already handles reconciliation. Also add a defensive bounds check in GetGroupCount to guard against other re-entrancy scenarios where UIKit calls back synchronously with a stale section index during DeleteSections. Fixes: https://github.com/dotnet/maui/issues/34691 --- .../Items/iOS/ObservableGroupedSource.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs index 93d36e034e6f..5a1a3125b18e 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs @@ -164,9 +164,19 @@ void CollectionChanged(NotifyCollectionChangedEventArgs args) if (!_collectionViewController.TryGetTarget(out var controller)) return; - // Force UICollectionView to get the internal accounting straight var collectionView = controller.CollectionView; - UpdateSection(collectionView); + + // Force UICollectionView to get the internal accounting straight. + // Skip for Remove: standard INotifyCollectionChanged semantics require the item to be + // already removed from the backing collection before CollectionChanged fires. This means + // _groupSource has N-1 items when we receive Remove, but UIKit still has N sections. + // Calling UpdateSection here triggers GetGroupCount with UIKit's stale section index + // (N-1), which is out of range on the already-mutated _groupSource → ArgumentOutOfRangeException. + // The post-processing UpdateSection call below handles reconciliation after DeleteSections. + if (args.Action != NotifyCollectionChangedAction.Remove) + { + UpdateSection(collectionView); + } switch (args.Action) { @@ -360,6 +370,12 @@ void Move(NotifyCollectionChangedEventArgs args) int GetGroupCount(int groupIndex) { + // Defensive bounds check: UIKit can call back synchronously during DeleteSections, + // and other re-entrancy scenarios may present a stale groupIndex. Return 0 instead + // of throwing ArgumentOutOfRangeException. + if ((uint)groupIndex >= (uint)_groupSource.Count) + return 0; + switch (_groupSource[groupIndex]) { case IList list: From de54a540a945321fe78e7aa9bcaf9489c04f4da4 Mon Sep 17 00:00:00 2001 From: baaaaif Date: Fri, 27 Mar 2026 08:07:03 +0100 Subject: [PATCH 2/6] test: add regression tests for grouped CollectionView section removal on iOS Adds three device tests to CollectionViewTests.iOS.cs verifying that removing sections from a grouped CollectionView does not throw ArgumentOutOfRangeException (issue #34691): - ItemsSourceGroupedRemoveLastSectionDoesNotCrash: removes the last group, which was the classic crash case where _groupSource[N-1] was accessed with only N-1 items. - ItemsSourceGroupedRemoveFirstSectionDoesNotCrash: removes from the beginning/middle. - ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash: removes all groups one by one, exercising the full range of section indices. --- .../CollectionView/CollectionViewTests.iOS.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs index 1a9dfd28b7ce..50c0201aeaed 100644 --- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs @@ -49,6 +49,114 @@ await CreateHandlerAndAddToWindow(collectionView, async h }); } + // Regression test for https://github.com/dotnet/maui/issues/34691 + // ObservableGroupedSource.CollectionChanged called UpdateSection() before processing + // Remove, causing GetGroupCount(N-1) to be invoked when _groupSource already had N-1 + // items → ArgumentOutOfRangeException. + [Fact(DisplayName = "Removing last section from grouped CollectionView does not crash")] + public async Task ItemsSourceGroupedRemoveLastSectionDoesNotCrash() + { + SetupBuilder(); + + var data = new List { "item 1", "item 2", "item 3" }; + var groupData = new ObservableCollection + { + new("Header 1", data), + new("Header 2", data), + new("Header 3", data), + }; + + var collectionView = new CollectionView + { + IsGrouped = true, + ItemsSource = groupData, + ItemTemplate = new DataTemplate(() => new Label()), + }; + + await CreateHandlerAndAddToWindow(collectionView, async handler => + { + await Task.Delay(1000); + + // Removing the last section was the classic crash: UIKit still had N sections + // when UpdateSection() was called before Remove(), so GetGroupCount(N-1) was + // invoked against a _groupSource that already had N-1 items → out of range. + groupData.RemoveAt(groupData.Count - 1); + + await Task.Delay(500); + + // Verify the CollectionView is still in a consistent state + Assert.Equal(2, groupData.Count); + }); + } + + // Regression test for https://github.com/dotnet/maui/issues/34691 + [Fact(DisplayName = "Removing first section from grouped CollectionView does not crash")] + public async Task ItemsSourceGroupedRemoveFirstSectionDoesNotCrash() + { + SetupBuilder(); + + var data = new List { "item 1", "item 2" }; + var groupData = new ObservableCollection + { + new("Header 1", data), + new("Header 2", data), + new("Header 3", data), + }; + + var collectionView = new CollectionView + { + IsGrouped = true, + ItemsSource = groupData, + ItemTemplate = new DataTemplate(() => new Label()), + }; + + await CreateHandlerAndAddToWindow(collectionView, async handler => + { + await Task.Delay(1000); + + groupData.RemoveAt(0); + await Task.Delay(500); + groupData.RemoveAt(0); + + Assert.Equal(1, groupData.Count); + }); + } + + // Regression test for https://github.com/dotnet/maui/issues/34691 + [Fact(DisplayName = "Removing sections one by one from grouped CollectionView does not crash")] + public async Task ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash() + { + SetupBuilder(); + + var data = new List { "item 1", "item 2" }; + var groupData = new ObservableCollection + { + new("Header 1", data), + new("Header 2", data), + new("Header 3", data), + }; + + var collectionView = new CollectionView + { + IsGrouped = true, + ItemsSource = groupData, + ItemTemplate = new DataTemplate(() => new Label()), + }; + + await CreateHandlerAndAddToWindow(collectionView, async handler => + { + await Task.Delay(1000); + + while (groupData.Count > 0) + { + groupData.RemoveAt(groupData.Count - 1); + await Task.Delay(300); + } + + Assert.Empty(groupData); + }); + } + class CollectionViewStringGroup : List { public string GroupHeader { get; private set; } From ea354b41ec6d9a7e3032fc58334782942432dcef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BCnzler=2C=20Nico?= Date: Mon, 20 Apr 2026 12:40:33 +0200 Subject: [PATCH 3/6] test: improve regression tests for grouped CollectionView section removal on iOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch from CollectionViewHandler (deprecated Items/) to CollectionViewHandler2 (default iOS handler, Items2/) which matches the production crash path from issue #34691. GroupableItemsViewController2 calls ItemsSourceFactory.CreateGrouped() which creates ObservableGroupedSource (Items/), so both handlers share the same source and the fix covers both. - Replace Task.Delay(1000) for initial load with WaitForUIUpdate(initialFrame, ...). - Replace Task.Delay(500/300) after each RemoveAt() with synchronous layout forcing via SetNeedsLayout() + LayoutIfNeeded(). This deterministically triggers the crash path (UpdateSection → NumberOfItemsInSection(N-1) → GetGroupCount(N-1) → ArgumentOutOfRangeException) without the fix, making the tests gate-worthy. - Add Assert.Equal(groupData.Count, handler.PlatformView.NumberOfSections()) to verify UIKit's section count is consistent with the .NET collection after removal. Fixes: #34691 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CollectionView/CollectionViewTests.iOS.cs | 80 ++++++++++++++----- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs index 50c0201aeaed..fb46375ac137 100644 --- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs @@ -49,11 +49,28 @@ await CreateHandlerAndAddToWindow(collectionView, async h }); } - // Regression test for https://github.com/dotnet/maui/issues/34691 - // ObservableGroupedSource.CollectionChanged called UpdateSection() before processing - // Remove, causing GetGroupCount(N-1) to be invoked when _groupSource already had N-1 - // items → ArgumentOutOfRangeException. - [Fact(DisplayName = "Removing last section from grouped CollectionView does not crash")] + // Regression tests for https://github.com/dotnet/maui/issues/34691 + // + // Root cause: ObservableGroupedSource.CollectionChanged() called UpdateSection() before + // processing Remove. INCC semantics require the item to be already removed from the + // backing collection before CollectionChanged fires, so _groupSource has N-1 items when + // MAUI receives Remove — but UIKit still has N sections. UpdateSection() then iterates + // UIKit's N sections and calls NumberOfItemsInSection(N-1), re-entering the data source: + // GetGroupCount(N-1) → _groupSource[N-1] → ArgumentOutOfRangeException. + // + // These tests use CollectionViewHandler2 (the default iOS handler) because the production + // crash in issue #34691 goes through ItemsViewController2 (Items2/), which calls + // ItemsSourceFactory.CreateGrouped() → ObservableGroupedSource (Items/). Both handlers + // share the same ObservableGroupedSource, so fixing it covers both paths. + // + // Each test forces a synchronous UIKit layout pass after the removal via SetNeedsLayout() + // + LayoutIfNeeded(). Before the fix this deterministically triggers the crash: + // CollectionChanged(Remove) → UpdateSection() → NumberOfItemsInSection(N-1) + // → GetGroupCount(N-1) → ArgumentOutOfRangeException. + // After the fix the pre-Remove UpdateSection() is skipped and GetGroupCount() has a + // defensive bounds check, so both the synchronous and the deferred layout paths are safe. + + [Fact(DisplayName = "Removing last section from grouped CollectionView does not crash (Handler2)")] public async Task ItemsSourceGroupedRemoveLastSectionDoesNotCrash() { SetupBuilder(); @@ -73,24 +90,31 @@ public async Task ItemsSourceGroupedRemoveLastSectionDoesNotCrash() ItemTemplate = new DataTemplate(() => new Label()), }; - await CreateHandlerAndAddToWindow(collectionView, async handler => + // Capture the pre-layout frame so WaitForUIUpdate can detect the first layout pass. + var initialFrame = collectionView.Frame; + + await CreateHandlerAndAddToWindow(collectionView, async handler => { - await Task.Delay(1000); + // Wait for the initial layout rather than a fixed delay. + await WaitForUIUpdate(initialFrame, collectionView); - // Removing the last section was the classic crash: UIKit still had N sections - // when UpdateSection() was called before Remove(), so GetGroupCount(N-1) was - // invoked against a _groupSource that already had N-1 items → out of range. + // Removing the last section was the classic crash: UIKit still has N sections + // when UpdateSection() is called before Remove(), so GetGroupCount(N-1) is + // invoked against a _groupSource that already has N-1 items → out of range. groupData.RemoveAt(groupData.Count - 1); - await Task.Delay(500); + // Force a synchronous layout pass so UIKit queries the data source immediately. + // Without the fix this deterministically throws ArgumentOutOfRangeException. + handler.PlatformView.SetNeedsLayout(); + handler.PlatformView.LayoutIfNeeded(); - // Verify the CollectionView is still in a consistent state Assert.Equal(2, groupData.Count); + // Verify UIKit's section count is consistent with the .NET collection. + Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); }); } - // Regression test for https://github.com/dotnet/maui/issues/34691 - [Fact(DisplayName = "Removing first section from grouped CollectionView does not crash")] + [Fact(DisplayName = "Removing first section from grouped CollectionView does not crash (Handler2)")] public async Task ItemsSourceGroupedRemoveFirstSectionDoesNotCrash() { SetupBuilder(); @@ -110,20 +134,26 @@ public async Task ItemsSourceGroupedRemoveFirstSectionDoesNotCrash() ItemTemplate = new DataTemplate(() => new Label()), }; - await CreateHandlerAndAddToWindow(collectionView, async handler => + var initialFrame = collectionView.Frame; + + await CreateHandlerAndAddToWindow(collectionView, async handler => { - await Task.Delay(1000); + await WaitForUIUpdate(initialFrame, collectionView); groupData.RemoveAt(0); - await Task.Delay(500); + handler.PlatformView.SetNeedsLayout(); + handler.PlatformView.LayoutIfNeeded(); + groupData.RemoveAt(0); + handler.PlatformView.SetNeedsLayout(); + handler.PlatformView.LayoutIfNeeded(); Assert.Equal(1, groupData.Count); + Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); }); } - // Regression test for https://github.com/dotnet/maui/issues/34691 - [Fact(DisplayName = "Removing sections one by one from grouped CollectionView does not crash")] + [Fact(DisplayName = "Removing all sections one by one from grouped CollectionView does not crash (Handler2)")] public async Task ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash() { SetupBuilder(); @@ -143,17 +173,23 @@ public async Task ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash() ItemTemplate = new DataTemplate(() => new Label()), }; - await CreateHandlerAndAddToWindow(collectionView, async handler => + var initialFrame = collectionView.Frame; + + await CreateHandlerAndAddToWindow(collectionView, async handler => { - await Task.Delay(1000); + await WaitForUIUpdate(initialFrame, collectionView); while (groupData.Count > 0) { groupData.RemoveAt(groupData.Count - 1); - await Task.Delay(300); + // Force synchronous layout after each removal to exercise the full range + // of section indices and confirm no re-entrancy crash occurs. + handler.PlatformView.SetNeedsLayout(); + handler.PlatformView.LayoutIfNeeded(); } Assert.Empty(groupData); + Assert.Equal(0, (int)handler.PlatformView.NumberOfSections()); }); } From 4d933deb74cabd0792716369dd5298e26a315e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BCnzler=2C=20Nico?= Date: Mon, 20 Apr 2026 19:21:05 +0200 Subject: [PATCH 4/6] test: fix build errors in grouped CollectionView regression tests - Remove handler.PlatformView.NumberOfSections() assertions: for CollectionViewHandler2, PlatformView is a UICollectionViewController subclass where NumberOfSections(UICollectionView) is the data source delegate callback requiring a UICollectionView argument, not a no-arg section count query. The crash-free execution of the test body is the meaningful gate assertion; a redundant UIKit section count check is not required. - Fix xUnit2013: replace Assert.Equal(1, groupData.Count) with Assert.Single(groupData). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Elements/CollectionView/CollectionViewTests.iOS.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs index fb46375ac137..bac05398c5f7 100644 --- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs @@ -109,8 +109,6 @@ await CreateHandlerAndAddToWindow(collectionView, async handler.PlatformView.LayoutIfNeeded(); Assert.Equal(2, groupData.Count); - // Verify UIKit's section count is consistent with the .NET collection. - Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); }); } @@ -148,8 +146,7 @@ await CreateHandlerAndAddToWindow(collectionView, async handler.PlatformView.SetNeedsLayout(); handler.PlatformView.LayoutIfNeeded(); - Assert.Equal(1, groupData.Count); - Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); + Assert.Single(groupData); }); } @@ -189,7 +186,6 @@ await CreateHandlerAndAddToWindow(collectionView, async } Assert.Empty(groupData); - Assert.Equal(0, (int)handler.PlatformView.NumberOfSections()); }); } From a7a523abd285b0292510b499e08d1af4dfbda424 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 13:26:50 +0200 Subject: [PATCH 5/6] Improve test assertions and trim verbose comments Address AI review feedback: - Replace trivial groupData.Count assertions with handler.PlatformView.NumberOfSections() to verify UIKit state - Trim 14-line block comment to concise 4-line summary - Remove verbose inline comments from test methods Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CollectionView/CollectionViewTests.iOS.cs | 38 ++++--------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs index bac05398c5f7..4ba180f32f16 100644 --- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs @@ -50,25 +50,9 @@ await CreateHandlerAndAddToWindow(collectionView, async h } // Regression tests for https://github.com/dotnet/maui/issues/34691 - // - // Root cause: ObservableGroupedSource.CollectionChanged() called UpdateSection() before - // processing Remove. INCC semantics require the item to be already removed from the - // backing collection before CollectionChanged fires, so _groupSource has N-1 items when - // MAUI receives Remove — but UIKit still has N sections. UpdateSection() then iterates - // UIKit's N sections and calls NumberOfItemsInSection(N-1), re-entering the data source: - // GetGroupCount(N-1) → _groupSource[N-1] → ArgumentOutOfRangeException. - // - // These tests use CollectionViewHandler2 (the default iOS handler) because the production - // crash in issue #34691 goes through ItemsViewController2 (Items2/), which calls - // ItemsSourceFactory.CreateGrouped() → ObservableGroupedSource (Items/). Both handlers - // share the same ObservableGroupedSource, so fixing it covers both paths. - // - // Each test forces a synchronous UIKit layout pass after the removal via SetNeedsLayout() - // + LayoutIfNeeded(). Before the fix this deterministically triggers the crash: - // CollectionChanged(Remove) → UpdateSection() → NumberOfItemsInSection(N-1) - // → GetGroupCount(N-1) → ArgumentOutOfRangeException. - // After the fix the pre-Remove UpdateSection() is skipped and GetGroupCount() has a - // defensive bounds check, so both the synchronous and the deferred layout paths are safe. + // ObservableGroupedSource.CollectionChanged() called UpdateSection() before Remove, + // causing GetGroupCount to access a stale section index → ArgumentOutOfRangeException. + // Each test forces a synchronous UIKit layout pass after removal to exercise the fix. [Fact(DisplayName = "Removing last section from grouped CollectionView does not crash (Handler2)")] public async Task ItemsSourceGroupedRemoveLastSectionDoesNotCrash() @@ -90,25 +74,18 @@ public async Task ItemsSourceGroupedRemoveLastSectionDoesNotCrash() ItemTemplate = new DataTemplate(() => new Label()), }; - // Capture the pre-layout frame so WaitForUIUpdate can detect the first layout pass. var initialFrame = collectionView.Frame; await CreateHandlerAndAddToWindow(collectionView, async handler => { - // Wait for the initial layout rather than a fixed delay. await WaitForUIUpdate(initialFrame, collectionView); - // Removing the last section was the classic crash: UIKit still has N sections - // when UpdateSection() is called before Remove(), so GetGroupCount(N-1) is - // invoked against a _groupSource that already has N-1 items → out of range. groupData.RemoveAt(groupData.Count - 1); - // Force a synchronous layout pass so UIKit queries the data source immediately. - // Without the fix this deterministically throws ArgumentOutOfRangeException. handler.PlatformView.SetNeedsLayout(); handler.PlatformView.LayoutIfNeeded(); - Assert.Equal(2, groupData.Count); + Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); }); } @@ -141,12 +118,12 @@ await CreateHandlerAndAddToWindow(collectionView, async groupData.RemoveAt(0); handler.PlatformView.SetNeedsLayout(); handler.PlatformView.LayoutIfNeeded(); + Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); groupData.RemoveAt(0); handler.PlatformView.SetNeedsLayout(); handler.PlatformView.LayoutIfNeeded(); - - Assert.Single(groupData); + Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); }); } @@ -179,10 +156,9 @@ await CreateHandlerAndAddToWindow(collectionView, async while (groupData.Count > 0) { groupData.RemoveAt(groupData.Count - 1); - // Force synchronous layout after each removal to exercise the full range - // of section indices and confirm no re-entrancy crash occurs. handler.PlatformView.SetNeedsLayout(); handler.PlatformView.LayoutIfNeeded(); + Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); } Assert.Empty(groupData); From 1f0029740d8a95f569bee5cb4967fa4f3f9f1bbe Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 21:04:18 +0200 Subject: [PATCH 6/6] Fix test build: use Controller.CollectionView for NumberOfSections handler.PlatformView for CollectionViewHandler2 is UIView, not UICollectionView. Access the actual UICollectionView through handler.Controller.CollectionView which has the NumberOfSections() method and SetNeedsLayout/LayoutIfNeeded for forcing UIKit layout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CollectionView/CollectionViewTests.iOS.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs index 4ba180f32f16..cfb18040853f 100644 --- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs @@ -79,13 +79,14 @@ public async Task ItemsSourceGroupedRemoveLastSectionDoesNotCrash() await CreateHandlerAndAddToWindow(collectionView, async handler => { await WaitForUIUpdate(initialFrame, collectionView); + var uiCollectionView = handler.Controller.CollectionView; groupData.RemoveAt(groupData.Count - 1); - handler.PlatformView.SetNeedsLayout(); - handler.PlatformView.LayoutIfNeeded(); + uiCollectionView.SetNeedsLayout(); + uiCollectionView.LayoutIfNeeded(); - Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); + Assert.Equal(groupData.Count, (int)uiCollectionView.NumberOfSections()); }); } @@ -114,16 +115,17 @@ public async Task ItemsSourceGroupedRemoveFirstSectionDoesNotCrash() await CreateHandlerAndAddToWindow(collectionView, async handler => { await WaitForUIUpdate(initialFrame, collectionView); + var uiCollectionView = handler.Controller.CollectionView; groupData.RemoveAt(0); - handler.PlatformView.SetNeedsLayout(); - handler.PlatformView.LayoutIfNeeded(); - Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); + uiCollectionView.SetNeedsLayout(); + uiCollectionView.LayoutIfNeeded(); + Assert.Equal(groupData.Count, (int)uiCollectionView.NumberOfSections()); groupData.RemoveAt(0); - handler.PlatformView.SetNeedsLayout(); - handler.PlatformView.LayoutIfNeeded(); - Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); + uiCollectionView.SetNeedsLayout(); + uiCollectionView.LayoutIfNeeded(); + Assert.Equal(groupData.Count, (int)uiCollectionView.NumberOfSections()); }); } @@ -152,13 +154,14 @@ public async Task ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash() await CreateHandlerAndAddToWindow(collectionView, async handler => { await WaitForUIUpdate(initialFrame, collectionView); + var uiCollectionView = handler.Controller.CollectionView; while (groupData.Count > 0) { groupData.RemoveAt(groupData.Count - 1); - handler.PlatformView.SetNeedsLayout(); - handler.PlatformView.LayoutIfNeeded(); - Assert.Equal(groupData.Count, (int)handler.PlatformView.NumberOfSections()); + uiCollectionView.SetNeedsLayout(); + uiCollectionView.LayoutIfNeeded(); + Assert.Equal(groupData.Count, (int)uiCollectionView.NumberOfSections()); } Assert.Empty(groupData);