Skip to content

Commit b1e8291

Browse files
SyedAbdulAzeemSF4852PureWeen
authored andcommitted
[iOS] Fix CollectionView ScrollOffset not resetting when ItemsSource changes (#34488)
<!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - On iOS, when using CollectionView with the Scrolled event, the VerticalOffset value doesn't reset to 0 when changing the ItemsSource. While the collection view displays the new items correctly, the reported scroll position remains at the previous offset value instead of resetting to the top position. ### Root Cause - On iOS, UICollectionView.ReloadData() does not reset ContentOffset. ContentOffset retains its previous value, meaning any subsequent Scrolled callback—which derives VerticalOffset and delta values directly from ContentOffset—reports the stale, non-zero offset. As a result, MAUI's VerticalOffset never resets to 0 after the ItemsSource changes. ### Description of Change - Added logic in ItemsViewController.cs and ItemsViewController2.cs to reset CollectionView.ContentOffset to zero only when it’s not already at the origin. This ensures the scroll position resets correctly when the ItemsSource changes on iOS/MacCatalyst. - Before resetting the offset, ResetScrollTracking() is invoked via the internal IScrollTrackingDelegator interface so that the scrollViewDidScroll callback computes the delta from zero instead of using a stale offset. **Test and UI improvements:** - Updated the test page (Issue7993.xaml) to add a "ScrollToEnd" button for reliably scrolling the list, and updated label and button identifiers for improved test automation. - Added the ScrollToEndClicked handler in Issue7993.xaml.cs to programmatically scroll to the end of the list, supporting the new test flow. - Removed platform-specific test skips and refactored the test (Issue7993.cs) to use the new "ScrollToEnd" and "NewItemsSource" buttons, improving reliability and making the test applicable to iOS/MacCatalyst. ### Issues Fixed Fixes #26366 Fixes #33500 ### Validated the behaviour in the following platforms - [ ] Windows - [ ] Android - [x] iOS - [x] Mac ### Output | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/a374859f-25ce-445f-a53f-6354c2d3f201"> | <video src="https://github.com/user-attachments/assets/2bdc2dd4-43be-41ef-810c-e4098cba791f"> |
1 parent cf41f29 commit b1e8291

8 files changed

Lines changed: 59 additions & 16 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Microsoft.Maui.Controls.Handlers.Items;
2+
3+
// Implemented by delegators to allow explicit scroll-tracking reset when ItemsSource changes.
4+
internal interface IScrollTrackingDelegator
5+
{
6+
void ResetScrollTracking();
7+
}

src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,15 @@ public virtual void UpdateItemsSource()
427427
CollectionView.ReloadData();
428428
CollectionView.CollectionViewLayout.InvalidateLayout();
429429

430+
// iOS/MacCatalyst: UIKit does not reset ContentOffset during ReloadData.
431+
// ResetScrollTracking must run before the assignment so the UIKit-triggered
432+
// scrollViewDidScroll callback computes delta from zero, not the stale previous offset.
433+
if (CollectionView.ContentOffset != CoreGraphics.CGPoint.Empty)
434+
{
435+
(Delegator as IScrollTrackingDelegator)?.ResetScrollTracking();
436+
CollectionView.ContentOffset = CoreGraphics.CGPoint.Empty;
437+
}
438+
430439
(ItemsView as IView)?.InvalidateMeasure();
431440
}
432441

src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
namespace Microsoft.Maui.Controls.Handlers.Items
1111
{
12-
public class ItemsViewDelegator<TItemsView, TViewController> : UICollectionViewDelegateFlowLayout
12+
public class ItemsViewDelegator<TItemsView, TViewController> : UICollectionViewDelegateFlowLayout, IScrollTrackingDelegator
1313
where TItemsView : ItemsView
1414
where TViewController : ItemsViewController<TItemsView>
1515
{
@@ -26,6 +26,12 @@ public ItemsViewDelegator(ItemsViewLayout itemsViewLayout, TViewController items
2626
_viewController = new(itemsViewController);
2727
}
2828

29+
void IScrollTrackingDelegator.ResetScrollTracking()
30+
{
31+
PreviousHorizontalOffset = 0;
32+
PreviousVerticalOffset = 0;
33+
}
34+
2935
public override void Scrolled(UIScrollView scrollView)
3036
{
3137
var (visibleItems, firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex) = GetVisibleItemsIndex();

src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using CoreGraphics;
88
using Foundation;
9+
using Microsoft.Maui.Controls.Handlers.Items;
910
using Microsoft.Maui.Controls.Internals;
1011
using Microsoft.Maui.Graphics;
1112
using PassKit;
@@ -298,6 +299,15 @@ public virtual void UpdateItemsSource()
298299
ReloadData();
299300
CollectionView.CollectionViewLayout.InvalidateLayout();
300301

302+
// iOS/MacCatalyst: UIKit does not reset ContentOffset during ReloadData.
303+
// ResetScrollTracking must run before the assignment so the UIKit-triggered
304+
// scrollViewDidScroll callback computes delta from zero, not the stale previous offset.
305+
if (CollectionView.ContentOffset != CoreGraphics.CGPoint.Empty)
306+
{
307+
(Delegator as IScrollTrackingDelegator)?.ResetScrollTracking();
308+
CollectionView.ContentOffset = CoreGraphics.CGPoint.Empty;
309+
}
310+
301311
(ItemsView as IView)?.InvalidateMeasure();
302312
}
303313

src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace Microsoft.Maui.Controls.Handlers.Items2
1212
{
13-
public class ItemsViewDelegator2<TItemsView, TViewController> : UICollectionViewDelegateFlowLayout
13+
public class ItemsViewDelegator2<TItemsView, TViewController> : UICollectionViewDelegateFlowLayout, IScrollTrackingDelegator
1414
where TItemsView : ItemsView
1515
where TViewController : ItemsViewController2<TItemsView>
1616
{
@@ -27,6 +27,12 @@ public ItemsViewDelegator2(UICollectionViewLayout itemsViewLayout, TViewControll
2727
_viewController = new(ItemsViewController2);
2828
}
2929

30+
void IScrollTrackingDelegator.ResetScrollTracking()
31+
{
32+
PreviousHorizontalOffset = 0;
33+
PreviousVerticalOffset = 0;
34+
}
35+
3036
public override void Scrolled(UIScrollView scrollView)
3137
{
3238
var (visibleItems, firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex) = GetVisibleItemsIndex();

src/Controls/tests/TestCases.HostApp/Issues/XFIssue/Issue7993.xaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
</Grid.RowDefinitions>
1313

1414
<StackLayout Grid.Row="0" Orientation="Vertical" Spacing="5" BackgroundColor="Beige">
15-
<Label LineBreakMode="WordWrap" Text="Scroll down into the list to increase vertical offset. Click NewItemsSource to reset items source. Verify that vertical offset becomes zero." HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
16-
<Label x:Name="Label1" Text="VerticalOffset: 0" HorizontalTextAlignment="Center"/>
17-
<Button Text="NewItemsSource" Clicked="NewItemsSourceClicked" HorizontalOptions="Center"/>
15+
<Label LineBreakMode="WordWrap" Text="Tap ScrollToEnd to scroll the list, then tap NewItemsSource. Verify that vertical offset becomes zero." HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
16+
<Label x:Name="Label1" AutomationId="VerticalOffsetLabel" Text="VerticalOffset: 0" HorizontalTextAlignment="Center"/>
17+
<Button Text="ScrollToEnd" AutomationId="ScrollToEnd" Clicked="ScrollToEndClicked" HorizontalOptions="Center"/>
18+
<Button Text="NewItemsSource" AutomationId="NewItemsSource" Clicked="NewItemsSourceClicked" HorizontalOptions="Center"/>
1819
</StackLayout>
1920

20-
<CollectionView Grid.Row="1" AutomationId="CollectionView7993" ItemsSource="{Binding Items}" Scrolled="CollectionView_OnScrolled">
21+
<CollectionView x:Name="CollectionView7993" Grid.Row="1" AutomationId="CollectionView7993" ItemsSource="{Binding Items}" Scrolled="CollectionView_OnScrolled">
2122
<CollectionView.ItemsLayout>
2223
<LinearItemsLayout Orientation="Vertical" ItemSpacing="5"/>
2324
</CollectionView.ItemsLayout>

src/Controls/tests/TestCases.HostApp/Issues/XFIssue/Issue7993.xaml.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
1717
Label1.Text = "VerticalOffset: " + e.VerticalOffset;
1818
}
1919

20+
void ScrollToEndClicked(object sender, EventArgs e)
21+
{
22+
var vm = BindingContext as ViewModel7993;
23+
if (vm?.Items.Count > 0)
24+
{
25+
CollectionView7993.ScrollTo(vm.Items[vm.Items.Count - 1]);
26+
}
27+
}
28+
2029
void NewItemsSourceClicked(object sender, EventArgs e)
2130
{
2231
BindingContext = new ViewModel7993();
Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
#if TEST_FAILS_ON_CATALYST && TEST_FAILS_ON_IOS //In MacCatalyst, the DragCoordinates is not supported. On the iOS platform, scroll position is not reset while update the itemsource. Issue: https://github.com/dotnet/maui/issues/26366
2-
using NUnit.Framework;
1+
using NUnit.Framework;
32
using UITest.Appium;
43
using UITest.Core;
54

@@ -17,16 +16,12 @@ public Issue7993(TestDevice testDevice) : base(testDevice)
1716
[Category(UITestCategories.CollectionView)]
1817
public void CollectionViewVerticalOffset()
1918
{
20-
var colView = App.WaitForElement("CollectionView7993");
21-
19+
App.WaitForElement("CollectionView7993");
2220
App.WaitForElement("VerticalOffset: 0");
23-
App.DragCoordinates(colView.GetRect().Width - 10,
24-
colView.GetRect().Y + colView.GetRect().Height - 50,
25-
colView.GetRect().Width - 10,
26-
colView.GetRect().Y + 5);
21+
App.Tap("ScrollToEnd");
2722
App.WaitForElement("19");
23+
App.WaitForNoElement("VerticalOffset: 0");
2824
App.Tap("NewItemsSource");
2925
App.WaitForElement("VerticalOffset: 0");
3026
}
31-
}
32-
#endif
27+
}

0 commit comments

Comments
 (0)