From 9e8dca6aa7ef226e3b9757c85fb9416090642ed6 Mon Sep 17 00:00:00 2001 From: SyedAbdulAzeem Date: Wed, 25 Feb 2026 17:37:48 +0530 Subject: [PATCH 1/2] iOS: Fix incorrect ItemsViewScrolledEventArgs values in grouped CollectionView --- .../Handlers/Items/iOS/ItemsViewDelegator.cs | 3 +- .../Items2/iOS/ItemsViewDelegator2.cs | 3 +- .../TestCases.HostApp/Issues/Issue17664.cs | 120 ++++++++++++++++++ .../Tests/Issues/Issue17664.cs | 31 +++++ 4 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs index 622e72211714..b37d6fd8e53f 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs @@ -117,7 +117,8 @@ protected virtual (bool VisibleItems, NSIndexPath First, NSIndexPath Center, NSI if (collectionView is null) return default; - var indexPathsForVisibleItems = collectionView.IndexPathsForVisibleItems.OrderBy(x => x.Row).ToList(); + // Sort visible item index paths by section and then by row for consistent order in both grouped and ungrouped sources + var indexPathsForVisibleItems = collectionView.IndexPathsForVisibleItems.OrderBy(x => x.Section).ThenBy(x => x.Row).ToList(); var visibleItems = indexPathsForVisibleItems.Count > 0; NSIndexPath firstVisibleItemIndex = null, centerItemIndex = null, lastVisibleItemIndex = null; diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs index ec38e061e904..1d36fc20d139 100644 --- a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs +++ b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs @@ -118,7 +118,8 @@ protected virtual (bool VisibleItems, NSIndexPath First, NSIndexPath Center, NSI if (collectionView is null) return default; - var indexPathsForVisibleItems = collectionView.IndexPathsForVisibleItems.OrderBy(x => x.Row).ToList(); + // Sort visible item index paths by section and then by row for consistent order in both grouped and ungrouped sources + var indexPathsForVisibleItems = collectionView.IndexPathsForVisibleItems.OrderBy(x => x.Section).ThenBy(x => x.Row).ToList(); var visibleItems = indexPathsForVisibleItems.Count > 0; NSIndexPath firstVisibleItemIndex = null, centerItemIndex = null, lastVisibleItemIndex = null; diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs new file mode 100644 index 000000000000..b167c6f35d95 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs @@ -0,0 +1,120 @@ +using System.Collections.ObjectModel; + +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 17664, "Incorrect ItemsViewScrolledEventArgs in CollectionView when IsGrouped is set to true", PlatformAffected.iOS | PlatformAffected.Android)] +public class Issue17664 : ContentPage +{ + CollectionView _collectionView; + Label descriptionLabel; + ObservableCollection _groupedItems; + + public Issue17664() + { + Button scrollButton = new Button + { + AutomationId = "Issue17664ScrollBtn", + Text = "Scroll to Category C, Item #2" + }; + scrollButton.Clicked += ScrollButton_Clicked; + + descriptionLabel = new Label + { + AutomationId = "Issue17664DescriptionLabel", + Text = "Use the button above to scroll the CollectionView.", + FontSize = 14, + HorizontalOptions = LayoutOptions.Center + }; + + _collectionView = new CollectionView + { + IsGrouped = true, + GroupHeaderTemplate = new DataTemplate(() => + { + Label label = new Label + { + FontAttributes = FontAttributes.Bold, + BackgroundColor = Colors.LightGray, + Padding = 10 + }; + + label.SetBinding(Label.TextProperty, "Name"); + return label; + }), + ItemTemplate = new DataTemplate(() => + { + Label textLabel = new Label + { + FontAttributes = FontAttributes.Bold, + Padding = 30 + }; + + textLabel.SetBinding(Label.TextProperty, "."); + return textLabel; + }) + }; + + _collectionView.Scrolled += (s, e) => + { + var flatItems = _groupedItems.SelectMany(group => group).ToList(); + if (e.LastVisibleItemIndex < flatItems.Count) + { + descriptionLabel.Text = flatItems[e.LastVisibleItemIndex]; + } + }; + + List categories = new List { "Category A", "Category B", "Category C" }; + + _groupedItems = new ObservableCollection(); + + foreach (var category in categories) + { + List items = new List(); + + for (int i = 0; i < 5; i++) + { + items.Add($"{category} item #{i}"); + } + + _groupedItems.Add(new Issue17664_ItemModelGroup(category, items)); + } + + _collectionView.ItemsSource = _groupedItems; + + Grid grid = new Grid + { + RowSpacing = 10, + Padding = 10, + RowDefinitions = + { + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Star } + } + }; + + grid.Add(scrollButton, 0, 0); + grid.Add(descriptionLabel, 0, 1); + grid.Add(_collectionView, 0, 2); + + Content = grid; + } + + private void ScrollButton_Clicked(object sender, EventArgs e) + { + var targetGroup = _groupedItems.FirstOrDefault(group => group.Name == "Category C"); + var targetItem = targetGroup.FirstOrDefault(item => item == "Category C item #2"); + + _collectionView.ScrollTo(targetItem, targetGroup, ScrollToPosition.End); + } +} + +public class Issue17664_ItemModelGroup : ObservableCollection +{ + public string Name { get; set; } + + public Issue17664_ItemModelGroup(string name, IEnumerable items) : base(items) + { + Name = name; + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs new file mode 100644 index 000000000000..26b27865d236 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs @@ -0,0 +1,31 @@ +#if TEST_FAILS_ON_ANDROID // PR Link - https://github.com/dotnet/maui/pull/31437 +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue17664 : _IssuesUITest +{ + public Issue17664(TestDevice device) : base(device) + { + } + + public override string Issue => "Incorrect ItemsViewScrolledEventArgs in CollectionView when IsGrouped is set to true"; + + [Test] + [Category(UITestCategories.CollectionView)] + public void VerifyGroupedCollectionViewVisibleItemIndices() + { + App.WaitForElement("Issue17664ScrollBtn"); + App.Tap("Issue17664ScrollBtn"); + +#if WINDOWS + Thread.Sleep(1000); +#endif + + var resultItem = App.WaitForElement("Issue17664DescriptionLabel").GetText(); + Assert.That(resultItem, Is.EqualTo("Category C item #2")); + } +} +#endif From f2a0ee3c2f292d25ba8ae12e1b44afa341f2da8e Mon Sep 17 00:00:00 2001 From: SyedAbdulAzeem Date: Tue, 10 Mar 2026 13:11:03 +0530 Subject: [PATCH 2/2] Remove PlatformAffected.Android and restrict test in Windows --- .../tests/TestCases.HostApp/Issues/Issue17664.cs | 4 ++-- .../TestCases.Shared.Tests/Tests/Issues/Issue17664.cs | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs index b167c6f35d95..707e06ad5aa2 100644 --- a/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs @@ -2,7 +2,7 @@ namespace Maui.Controls.Sample.Issues; -[Issue(IssueTracker.Github, 17664, "Incorrect ItemsViewScrolledEventArgs in CollectionView when IsGrouped is set to true", PlatformAffected.iOS | PlatformAffected.Android)] +[Issue(IssueTracker.Github, 17664, "Incorrect ItemsViewScrolledEventArgs in CollectionView when IsGrouped is set to true", PlatformAffected.iOS)] public class Issue17664 : ContentPage { CollectionView _collectionView; @@ -57,7 +57,7 @@ public Issue17664() _collectionView.Scrolled += (s, e) => { var flatItems = _groupedItems.SelectMany(group => group).ToList(); - if (e.LastVisibleItemIndex < flatItems.Count) + if (e.LastVisibleItemIndex >= 0 && e.LastVisibleItemIndex < flatItems.Count) { descriptionLabel.Text = flatItems[e.LastVisibleItemIndex]; } diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs index 26b27865d236..71874bd0c082 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs @@ -1,4 +1,7 @@ -#if TEST_FAILS_ON_ANDROID // PR Link - https://github.com/dotnet/maui/pull/31437 +#if TEST_FAILS_ON_ANDROID && TEST_FAILS_ON_WINDOWS // Android fix: https://github.com/dotnet/maui/pull/31437 +// Windows: The Scrolled event is not consistently triggered in the CI environment during automated +// scrolling, so the label text is never updated. This is a test infrastructure limitation on Windows; +// the fix itself is iOS/MacCatalyst-only and works correctly on iOS and MacCatalyst. using NUnit.Framework; using UITest.Appium; using UITest.Core; @@ -20,10 +23,6 @@ public void VerifyGroupedCollectionViewVisibleItemIndices() App.WaitForElement("Issue17664ScrollBtn"); App.Tap("Issue17664ScrollBtn"); -#if WINDOWS - Thread.Sleep(1000); -#endif - var resultItem = App.WaitForElement("Issue17664DescriptionLabel").GetText(); Assert.That(resultItem, Is.EqualTo("Category C item #2")); }