diff --git a/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableItemsSource.cs b/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableItemsSource.cs
index 1889e038319e..07c0eefceeb2 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableItemsSource.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableItemsSource.cs
@@ -229,7 +229,7 @@ internal int ItemsCount()
internal object ElementAt(int index)
{
if (_itemsSource is IList list)
- return list[index];
+ return (index >= 0 && index < list.Count) ? list[index] : null;
int count = 0;
foreach (var item in _itemsSource)
@@ -239,7 +239,7 @@ internal object ElementAt(int index)
count++;
}
- return -1;
+ return null;
}
}
}
diff --git a/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs b/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
index ab43bb156c0a..e0b11f6650ad 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
@@ -374,15 +374,8 @@ protected virtual void UpdateItemsUpdatingScrollMode()
if (ItemsViewAdapter == null || ItemsView == null)
return;
- if (ItemsView.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepItemsInView)
- {
- // Keeping the current items in view is the default, so we don't need to watch for data changes
- _itemsUpdateScrollObserver.Stop(ItemsViewAdapter);
- }
- else
- {
- _itemsUpdateScrollObserver.Start(ItemsViewAdapter);
- }
+ // We need to observe the adapter for changes for the ItemsUpdatingScrollMode.
+ _itemsUpdateScrollObserver.Start(ItemsViewAdapter);
}
public virtual void ScrollTo(ScrollToRequestEventArgs args)
@@ -392,6 +385,11 @@ public virtual void ScrollTo(ScrollToRequestEventArgs args)
var position = DetermineTargetPosition(args);
+ if (position < 0)
+ {
+ return;
+ }
+
if (args.IsAnimated)
{
ScrollHelper.AnimateScrollToPosition(position, args.ScrollToPosition);
@@ -598,6 +596,13 @@ internal void AdjustScrollForItemUpdate()
{
ScrollHelper.UndoNextScrollAdjustment();
}
+ else if (ItemsView.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepItemsInView)
+ {
+ if (GetLayoutManager().ItemCount > 0)
+ {
+ ScrollTo(new ScrollToRequestEventArgs(0, 0, Microsoft.Maui.Controls.ScrollToPosition.Start, true));
+ }
+ }
}
GridLayoutManager CreateGridLayout(GridItemsLayout gridItemsLayout)
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewLayout.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewLayout.cs
index c6f8473b629d..9ee1a47ef2f1 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewLayout.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewLayout.cs
@@ -5,6 +5,7 @@
using CoreGraphics;
using Foundation;
using Microsoft.Extensions.Logging;
+using Microsoft.Maui.Controls.Handlers.Items2;
using UIKit;
namespace Microsoft.Maui.Controls.Handlers.Items
@@ -500,6 +501,10 @@ public override void FinalizeCollectionViewUpdates()
{
ForceScrollToLastItem(CollectionView, _itemsLayout);
}
+ else if (ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepItemsInView)
+ {
+ ForceScrollToFirstItem(CollectionView, _itemsLayout);
+ }
}
void TrackOffsetAdjustment()
@@ -565,6 +570,33 @@ static bool UpdateWillShiftVisibleItems(UICollectionView collectionView, UIColle
return false;
}
+ static void ForceScrollToFirstItem(UICollectionView collectionView, ItemsLayout itemsLayout)
+ {
+ //CarouselView and CarouselView2 handle scrolling based on ItemsUpdatingScrollMode in CarouselViewController. Even when passing the 0th index, issues may arise in CarouselView since the position is determined by the center itemindex.
+ // Issue Link : https://github.com/dotnet/maui/issues/25991
+ if (collectionView.DataSource is CarouselViewController || collectionView.DataSource is CarouselViewController2)
+ {
+ return;
+ }
+
+ //Fix Added for ItemsUpdatingScrollMode.KeepItemsInView
+ int sections = (int)collectionView.NumberOfSections();
+
+ if (sections == 0)
+ {
+ return;
+ }
+
+ if (collectionView.NumberOfItemsInSection(0) > 0)
+ {
+ var indexPath = NSIndexPath.FromItemSection(0, 0);
+ if (itemsLayout.Orientation == ItemsLayoutOrientation.Vertical)
+ collectionView.ScrollToItem(indexPath, UICollectionViewScrollPosition.Top, true);
+ else
+ collectionView.ScrollToItem(indexPath, UICollectionViewScrollPosition.Right, true);
+ }
+ }
+
static void ForceScrollToLastItem(UICollectionView collectionView, ItemsLayout itemsLayout)
{
var sections = (int)collectionView.NumberOfSections();
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue26810.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue26810.xaml
new file mode 100644
index 000000000000..4534f3e97ad9
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue26810.xaml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue26810.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue26810.xaml.cs
new file mode 100644
index 000000000000..ce46abb1e753
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue26810.xaml.cs
@@ -0,0 +1,51 @@
+using System.Collections.ObjectModel;
+
+namespace Maui.Controls.Sample.Issues;
+[Issue(IssueTracker.Github, 26810, "Scroll To first item in CollectionView when updating the collection with KeepItemsInView",
+ PlatformAffected.Android)]
+public partial class Issue26810 : ContentPage
+{
+ private ObservableCollection Items { get; set; } = new ObservableCollection();
+ public Issue26810()
+ {
+ InitializeComponent();
+ for (int i = 1; i <= 30; i++)
+ {
+ Items.Add(new Issue26810ItemModel { Name = $"Preloaded Item {i}", AutomationId = $"Item{i}" });
+ }
+ CollectionView.ItemsSource = Items;
+ this.BindingContext = this;
+ }
+
+ private void ItemsUpdatingScrollMode_Clicked(object sender, EventArgs e)
+ {
+ var button = (Button)sender;
+ if (button.Text == "KeepScrollOffset")
+ {
+ CollectionView.ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepScrollOffset;
+ }
+ else if (button.Text == "KeepLastItemInView")
+ {
+ CollectionView.ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepLastItemInView;
+ }
+ }
+
+ private void AddButton_Clicked(object sender, EventArgs e)
+ {
+ Items.Add(new Issue26810ItemModel { Name = $"Item {Items.Count + 1}", AutomationId = $"Item{Items.Count + 1}" });
+ }
+ private void ScrollToButton_Clicked(object sender, EventArgs e)
+ {
+ if (Items.Count > 0)
+ {
+ // Scroll to random item
+ CollectionView.ScrollTo(19, position: ScrollToPosition.End, animate: true);
+ }
+ }
+}
+
+public class Issue26810ItemModel
+{
+ public string Name { get; set; }
+ public string AutomationId { get; set; }
+}
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue26810Vertical.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue26810Vertical.xaml
new file mode 100644
index 000000000000..29a7632f3af0
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue26810Vertical.xaml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue26810Vertical.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue26810Vertical.xaml.cs
new file mode 100644
index 000000000000..7b386db71961
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue26810Vertical.xaml.cs
@@ -0,0 +1,47 @@
+using System.Collections.ObjectModel;
+
+namespace Maui.Controls.Sample.Issues;
+
+[Issue(IssueTracker.None, 26810, "Scroll To first item in CollectionView with vertical orientation when updating the collection with KeepItemsInView",
+ PlatformAffected.Android)]
+
+public partial class Issue26810Vertical : ContentPage
+{
+
+ private ObservableCollection Items { get; set; } = new ObservableCollection();
+ public Issue26810Vertical()
+ {
+ InitializeComponent();
+ for (int i = 1; i <= 30; i++)
+ {
+ Items.Add(new Issue26810ItemModel { Name = $"Preloaded Item {i}", AutomationId = $"Item{i}" });
+ }
+ CollectionView.ItemsSource = Items;
+ this.BindingContext = this;
+ }
+ private void ItemsUpdatingScrollMode_Clicked(object sender, EventArgs e)
+ {
+ var button = (Button)sender;
+ if (button.Text == "KeepScrollOffset")
+ {
+ CollectionView.ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepScrollOffset;
+ }
+ else if (button.Text == "KeepLastItemInView")
+ {
+ CollectionView.ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepLastItemInView;
+ }
+ }
+
+ private void AddButton_Clicked(object sender, EventArgs e)
+ {
+ Items.Add(new Issue26810ItemModel { Name = $"Item {Items.Count + 1}", AutomationId = $"Item{Items.Count + 1}" });
+ }
+ private void ScrollToButton_Clicked(object sender, EventArgs e)
+ {
+ if (Items.Count > 0)
+ {
+ // Scroll to random item
+ CollectionView.ScrollTo(19, position: ScrollToPosition.End, animate: true);
+ }
+ }
+}
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CollectionViewUITests.CollectionViewItemsUpdatingScrollMode.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CollectionViewUITests.CollectionViewItemsUpdatingScrollMode.cs
index 09a8d93c8444..52910baeb5ec 100644
--- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CollectionViewUITests.CollectionViewItemsUpdatingScrollMode.cs
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CollectionViewUITests.CollectionViewItemsUpdatingScrollMode.cs
@@ -15,7 +15,6 @@ public CollectionViewItemsUpdatingScrollModeUITests(TestDevice device)
protected override bool ResetAfterEachTest => true;
-#if TEST_FAILS_ON_WINDOWS // For more information, see :https://github.com/dotnet/maui/issues/28006
// KeepScrollOffset (src\Compatibility\ControlGallery\src\Issues.Shared\CollectionViewItemsUpdatingScrollMode.cs)
[Test]
[Category(UITestCategories.CollectionView)]
@@ -30,9 +29,9 @@ public void KeepItemsInView()
App.Click("AddItemAbove");
}
- App.WaitForElement("Vegetables.jpg, 10");
+ App.WaitForNoElement("Vegetables.jpg, 10");
}
-#endif
+
#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST // The test fails on iOS and macOS because Appium is unable to locate the Picker control elements resulting in a TimeoutException. For more information, see: https://github.com/dotnet/maui/issues/28024
// KeepScrollOffset (src\Compatibility\ControlGallery\src\Issues.Shared\CollectionViewItemsUpdatingScrollMode.cs)
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26810.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26810.cs
new file mode 100644
index 000000000000..3012ad80cc6d
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26810.cs
@@ -0,0 +1,54 @@
+using NUnit.Framework;
+using NUnit.Framework.Legacy;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues
+{
+ internal class Issue26810 : _IssuesUITest
+ {
+ public override string Issue => "Scroll To first item in CollectionView when updating the collection with KeepItemsInView";
+
+ public Issue26810(TestDevice device) : base(device)
+ {
+ }
+
+ [Test]
+ [Category(UITestCategories.CollectionView)]
+ public void ScrollToFirstItemOnCollectionChanged()
+ {
+ // Is a Android and iOS issue; see https://github.com/dotnet/maui/issues/26810
+ //KeepItemsInView
+ App.WaitForElement("Item1");
+ App.Click("26810Button");
+ //Scrolled to 20 th item
+ App.WaitForElement("Item20");
+ //Added a new item
+ App.Click("26810AddButton");
+ //Checking while item is scrolled to first position or not
+ App.WaitForElement("Item1");
+
+ //KeepScrollOffset
+ App.WaitForElement("Item1");
+ // Changing ItemsUpdatingScrollMode to KeepScrollOffset
+ App.Click("26810ScrollOffsetButton");
+ App.Click("26810Button");
+ //Scrolled to 20 th item
+ App.WaitForElement("Item20");
+ //Added a new item
+ App.Click("26810AddButton");
+ //Checking scroll position is maintained or not
+ App.WaitForElement("Item20");
+
+ //KeepScrollOffset
+ // Changing ItemsUpdatingScrollMode to KeepLastItemInView
+ App.WaitForElement("26810LastItemButton");
+ App.Click("26810LastItemButton");
+ //Added a new item
+ App.Click("26810AddButton");
+ //Checking Lastitem is visible or not
+ App.WaitForElement("Item33");
+
+ }
+ }
+}
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26810Vertical.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26810Vertical.cs
new file mode 100644
index 000000000000..6d4e8fc917c1
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26810Vertical.cs
@@ -0,0 +1,54 @@
+using NUnit.Framework;
+using NUnit.Framework.Legacy;
+using UITest.Appium;
+using UITest.Core;
+namespace Microsoft.Maui.TestCases.Tests.Issues;
+
+public class Issue26810Vertical : _IssuesUITest
+{
+ public override string Issue => "Scroll To first item in CollectionView with vertical orientation when updating the collection with KeepItemsInView";
+
+ public Issue26810Vertical(TestDevice device) : base(device)
+ {
+ }
+
+ [Test]
+ [Category(UITestCategories.CollectionView)]
+ public void ScrollToFirstItemOnCollectionChanged()
+ {
+ // Is a Android and iOS issue; see https://github.com/dotnet/maui/issues/26810
+ //KeepItemsInView
+ App.WaitForElement("Item1");
+ App.Click("26810Button");
+ //Scrolled to 20 th item
+ App.WaitForElement("Item20");
+ //Added a new item
+ App.Click("26810AddButton");
+ //Checking while item is scrolled to first position or not
+ App.WaitForElement("Item1");
+
+ //KeepScrollOffset
+ App.WaitForElement("Item1");
+ // Changing ItemsUpdatingScrollMode to KeepScrollOffset
+ App.Click("26810ScrollOffsetButton");
+ App.Click("26810Button");
+ //Scrolled to 20 th item
+ App.WaitForElement("Item20");
+ //Added a new item
+ App.Click("26810AddButton");
+ //Checking scroll position is maintained or not
+ App.WaitForElement("Item20");
+
+ //KeepScrollOffset
+ // Changing ItemsUpdatingScrollMode to KeepLastItemInView
+ App.WaitForElement("26810LastItemButton");
+ App.Click("26810LastItemButton");
+ //Added a new item
+ App.Click("26810AddButton");
+ //Checking Lastitem is visible or not
+ App.WaitForElement("Item33");
+
+ }
+}
+
+