diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs index 622e72211714..eab87d95519c 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs @@ -29,8 +29,10 @@ public override void Scrolled(UIScrollView scrollView) { var (visibleItems, firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex) = GetVisibleItemsIndex(); - if (!visibleItems) + if (!visibleItems && !HasHeaderOrFooter()) + { return; + } var contentInset = scrollView.ContentInset; var contentOffsetX = scrollView.ContentOffset.X + contentInset.Left; @@ -147,6 +149,37 @@ protected virtual (bool VisibleItems, int First, int Center, int Last) GetVisibl return (VisibleItems, firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex); } + bool HasHeaderOrFooter() + { + var viewController = ViewController; + + if (viewController?.ItemsView is null) + { + return false; + } + + // Check for structured headers/footers + if (viewController.ItemsView is StructuredItemsView structuredItemsView) + { + if (structuredItemsView.Header is not null || structuredItemsView.HeaderTemplate is not null || + structuredItemsView.Footer is not null || structuredItemsView.FooterTemplate is not null) + { + return true; + } + } + + // Check for group headers/footers + if (viewController.ItemsView is GroupableItemsView groupableItemsView && groupableItemsView.IsGrouped) + { + if (groupableItemsView.GroupHeaderTemplate is not null || groupableItemsView.GroupFooterTemplate is not null) + { + return true; + } + } + + return false; + } + static int GetItemIndex(NSIndexPath indexPath, IItemsViewSource itemSource) { int index = (int)indexPath.Item; diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs index ec38e061e904..ade543aa26ce 100644 --- a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs +++ b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs @@ -30,8 +30,10 @@ public override void Scrolled(UIScrollView scrollView) { var (visibleItems, firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex) = GetVisibleItemsIndex(); - if (!visibleItems) + if (!visibleItems && !HasHeaderOrFooter()) + { return; + } var contentInset = scrollView.ContentInset; var contentOffsetX = scrollView.ContentOffset.X + contentInset.Left; @@ -148,6 +150,37 @@ protected virtual (bool VisibleItems, int First, int Center, int Last) GetVisibl return (VisibleItems, firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex); } + bool HasHeaderOrFooter() + { + var viewController = ViewController; + + if (viewController?.ItemsView is null) + { + return false; + } + + // Check for structured headers/footers (overall CollectionView header/footer) + if (viewController.ItemsView is StructuredItemsView structuredItemsView) + { + if (structuredItemsView.Header is not null || structuredItemsView.HeaderTemplate is not null || + structuredItemsView.Footer is not null || structuredItemsView.FooterTemplate is not null) + { + return true; + } + } + + // Check for group headers/footers (grouped CollectionView) + if (viewController.ItemsView is GroupableItemsView groupableItemsView && groupableItemsView.IsGrouped) + { + if (groupableItemsView.GroupHeaderTemplate is not null || groupableItemsView.GroupFooterTemplate is not null) + { + return true; + } + } + + return false; + } + static int GetItemIndex(NSIndexPath indexPath, IItemsViewSource itemSource) { int index = (int)indexPath.Item; diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue30249.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue30249.cs new file mode 100644 index 000000000000..50c17009aa58 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue30249.cs @@ -0,0 +1,83 @@ +using System.Collections.ObjectModel; + +namespace Controls.TestCases.HostApp.Issues; + +[Issue(IssueTracker.Github, 30249, "Grouped CollectionView does not trigger the Scrolled event for empty groups", PlatformAffected.iOS)] +public class Issue30249 : ContentPage +{ + CollectionView collectionViewWithEmptyGroups; + ObservableCollection groupedItems = new ObservableCollection(); + Label scrolledEventStatusLabel; + + public Issue30249() + { + Label descriptionLabel = new Label + { + Text = "Verify CollectionView Scrolled event is triggered for empty groups", + Margin = new Thickness(10) + }; + + scrolledEventStatusLabel = new Label + { + AutomationId = "ScrolledEventStatusLabel", + Text = "Failure", + Margin = new Thickness(10) + }; + + collectionViewWithEmptyGroups = new CollectionView + { + AutomationId = "CollectionViewWithEmptyGroups", + IsGrouped = true + }; + collectionViewWithEmptyGroups.Scrolled += CollectionView_Scrolled; + + collectionViewWithEmptyGroups.GroupHeaderTemplate = new DataTemplate(() => + { + Label label = new Label + { + Padding = new Thickness(10), + }; + + label.SetBinding(Label.TextProperty, "Title"); + return label; + }); + + for (int group = 0; group < 20; group++) + { + groupedItems.Add(new Issue30249Group($"Group {group}", new List())); + } + + collectionViewWithEmptyGroups.ItemsSource = groupedItems; + + Grid grid = new Grid + { + RowDefinitions = + { + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Star } + } + }; + + grid.Add(descriptionLabel, 0, 0); + grid.Add(scrolledEventStatusLabel, 0, 1); + grid.Add(collectionViewWithEmptyGroups, 0, 2); + + Content = grid; + } + + private void CollectionView_Scrolled(object sender, ItemsViewScrolledEventArgs e) + { + scrolledEventStatusLabel.Text = "Success"; + } +} + +public class Issue30249Group : List +{ + public string Title { get; set; } + + public Issue30249Group(string title, List items) : base(items) + { + Title = title; + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30249.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30249.cs new file mode 100644 index 000000000000..2945e9b22bce --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30249.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue30249 : _IssuesUITest +{ + public Issue30249(TestDevice device) : base(device) + { + } + + public override string Issue => "Grouped CollectionView does not trigger the Scrolled event for empty groups"; + + [Test] + [Category(UITestCategories.CollectionView)] + public void VerifyScrolledEventForEmptyGroups() + { + App.WaitForElement("CollectionViewWithEmptyGroups"); + App.ScrollDown("CollectionViewWithEmptyGroups", ScrollStrategy.Gesture, 0.2, 500); + + var scrolledEventStatus = App.FindElement("ScrolledEventStatusLabel").GetText(); + Assert.That(scrolledEventStatus, Is.EqualTo("Success")); + } +} \ No newline at end of file