-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[iOS] Support for KeepLastItemInView for CollectionView2 on iOS #31104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -83,7 +83,7 @@ static NSCollectionLayoutBoundarySupplementaryItem[] CreateSupplementaryItems(La | |||||||||||||||
return []; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
static UICollectionViewLayout CreateListLayout(UICollectionViewScrollDirection scrollDirection, LayoutGroupingInfo groupingInfo, LayoutHeaderFooterInfo layoutHeaderFooterInfo, LayoutSnapInfo snapInfo, NSCollectionLayoutDimension itemWidth, NSCollectionLayoutDimension itemHeight, NSCollectionLayoutDimension groupWidth, NSCollectionLayoutDimension groupHeight, double itemSpacing, Func<Thickness>? peekAreaInsetsFunc) | ||||||||||||||||
static UICollectionViewLayout CreateListLayout(UICollectionViewScrollDirection scrollDirection, LayoutGroupingInfo groupingInfo, LayoutHeaderFooterInfo layoutHeaderFooterInfo, LayoutSnapInfo snapInfo, NSCollectionLayoutDimension itemWidth, NSCollectionLayoutDimension itemHeight, NSCollectionLayoutDimension groupWidth, NSCollectionLayoutDimension groupHeight, double itemSpacing, Func<Thickness>? peekAreaInsetsFunc, ItemsUpdatingScrollMode itemsUpdatingScrollMode) | ||||||||||||||||
{ | ||||||||||||||||
var layoutConfiguration = new UICollectionViewCompositionalLayoutConfiguration(); | ||||||||||||||||
layoutConfiguration.ScrollDirection = scrollDirection; | ||||||||||||||||
|
@@ -137,14 +137,14 @@ static UICollectionViewLayout CreateListLayout(UICollectionViewScrollDirection s | |||||||||||||||
groupHeight); | ||||||||||||||||
|
||||||||||||||||
return section; | ||||||||||||||||
}, layoutConfiguration); | ||||||||||||||||
}, layoutConfiguration, itemsUpdatingScrollMode); | ||||||||||||||||
|
||||||||||||||||
return layout; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
static UICollectionViewLayout CreateGridLayout(UICollectionViewScrollDirection scrollDirection, LayoutGroupingInfo groupingInfo, LayoutHeaderFooterInfo headerFooterInfo, LayoutSnapInfo snapInfo, NSCollectionLayoutDimension itemWidth, NSCollectionLayoutDimension itemHeight, NSCollectionLayoutDimension groupWidth, NSCollectionLayoutDimension groupHeight, double verticalItemSpacing, double horizontalItemSpacing, int columns) | ||||||||||||||||
static UICollectionViewLayout CreateGridLayout(UICollectionViewScrollDirection scrollDirection, LayoutGroupingInfo groupingInfo, LayoutHeaderFooterInfo headerFooterInfo, LayoutSnapInfo snapInfo, NSCollectionLayoutDimension itemWidth, NSCollectionLayoutDimension itemHeight, NSCollectionLayoutDimension groupWidth, NSCollectionLayoutDimension groupHeight, double verticalItemSpacing, double horizontalItemSpacing, int columns, ItemsUpdatingScrollMode itemsUpdatingScrollMode) | ||||||||||||||||
{ | ||||||||||||||||
var layoutConfiguration = new UICollectionViewCompositionalLayoutConfiguration(); | ||||||||||||||||
layoutConfiguration.ScrollDirection = scrollDirection; | ||||||||||||||||
|
@@ -189,7 +189,7 @@ static UICollectionViewLayout CreateGridLayout(UICollectionViewScrollDirection s | |||||||||||||||
groupHeight); | ||||||||||||||||
|
||||||||||||||||
return section; | ||||||||||||||||
}, layoutConfiguration); | ||||||||||||||||
}, layoutConfiguration, itemsUpdatingScrollMode); | ||||||||||||||||
|
||||||||||||||||
return layout; | ||||||||||||||||
} | ||||||||||||||||
|
@@ -207,7 +207,8 @@ public static UICollectionViewLayout CreateVerticalList(LinearItemsLayout linear | |||||||||||||||
NSCollectionLayoutDimension.CreateFractionalWidth(1f), | ||||||||||||||||
NSCollectionLayoutDimension.CreateEstimated(30f), | ||||||||||||||||
linearItemsLayout.ItemSpacing, | ||||||||||||||||
null); | ||||||||||||||||
null, | ||||||||||||||||
linearItemsLayout.ItemsUpdatingScrollMode); | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
public static UICollectionViewLayout CreateHorizontalList(LinearItemsLayout linearItemsLayout, | ||||||||||||||||
|
@@ -223,7 +224,8 @@ public static UICollectionViewLayout CreateHorizontalList(LinearItemsLayout line | |||||||||||||||
NSCollectionLayoutDimension.CreateEstimated(30f), | ||||||||||||||||
NSCollectionLayoutDimension.CreateFractionalHeight(1f), | ||||||||||||||||
linearItemsLayout.ItemSpacing, | ||||||||||||||||
null); | ||||||||||||||||
null, | ||||||||||||||||
linearItemsLayout.ItemsUpdatingScrollMode); | ||||||||||||||||
|
||||||||||||||||
public static UICollectionViewLayout CreateVerticalGrid(GridItemsLayout gridItemsLayout, | ||||||||||||||||
LayoutGroupingInfo groupingInfo, LayoutHeaderFooterInfo headerFooterInfo) | ||||||||||||||||
|
@@ -241,7 +243,8 @@ public static UICollectionViewLayout CreateVerticalGrid(GridItemsLayout gridItem | |||||||||||||||
NSCollectionLayoutDimension.CreateEstimated(30f), | ||||||||||||||||
gridItemsLayout.VerticalItemSpacing, | ||||||||||||||||
gridItemsLayout.HorizontalItemSpacing, | ||||||||||||||||
gridItemsLayout.Span); | ||||||||||||||||
gridItemsLayout.Span, | ||||||||||||||||
gridItemsLayout.ItemsUpdatingScrollMode); | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
public static UICollectionViewLayout CreateHorizontalGrid(GridItemsLayout gridItemsLayout, | ||||||||||||||||
|
@@ -260,7 +263,8 @@ public static UICollectionViewLayout CreateHorizontalGrid(GridItemsLayout gridIt | |||||||||||||||
NSCollectionLayoutDimension.CreateFractionalHeight(1f), | ||||||||||||||||
gridItemsLayout.VerticalItemSpacing, | ||||||||||||||||
gridItemsLayout.HorizontalItemSpacing, | ||||||||||||||||
gridItemsLayout.Span); | ||||||||||||||||
gridItemsLayout.Span, | ||||||||||||||||
gridItemsLayout.ItemsUpdatingScrollMode); | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
#nullable disable | ||||||||||||||||
|
@@ -399,9 +403,50 @@ public static UICollectionViewLayout CreateCarouselLayout( | |||||||||||||||
class CustomUICollectionViewCompositionalLayout : UICollectionViewCompositionalLayout | ||||||||||||||||
{ | ||||||||||||||||
LayoutSnapInfo _snapInfo; | ||||||||||||||||
public CustomUICollectionViewCompositionalLayout(LayoutSnapInfo snapInfo, UICollectionViewCompositionalLayoutSectionProvider sectionProvider, UICollectionViewCompositionalLayoutConfiguration configuration) : base(sectionProvider, configuration) | ||||||||||||||||
ItemsUpdatingScrollMode _itemsUpdatingScrollMode; | ||||||||||||||||
|
||||||||||||||||
public CustomUICollectionViewCompositionalLayout(LayoutSnapInfo snapInfo, UICollectionViewCompositionalLayoutSectionProvider sectionProvider, UICollectionViewCompositionalLayoutConfiguration configuration, ItemsUpdatingScrollMode itemsUpdatingScrollMode) : base(sectionProvider, configuration) | ||||||||||||||||
{ | ||||||||||||||||
_snapInfo = snapInfo; | ||||||||||||||||
_itemsUpdatingScrollMode = itemsUpdatingScrollMode; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
public override void FinalizeCollectionViewUpdates() | ||||||||||||||||
{ | ||||||||||||||||
base.FinalizeCollectionViewUpdates(); | ||||||||||||||||
|
||||||||||||||||
if (_itemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepLastItemInView) | ||||||||||||||||
{ | ||||||||||||||||
ForceScrollToLastItem(CollectionView); | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
void ForceScrollToLastItem(UICollectionView collectionView) | ||||||||||||||||
{ | ||||||||||||||||
var sections = (int)collectionView.NumberOfSections(); | ||||||||||||||||
|
||||||||||||||||
if (sections == 0) | ||||||||||||||||
{ | ||||||||||||||||
return; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
for (int section = sections - 1; section >= 0; section--) | ||||||||||||||||
{ | ||||||||||||||||
var itemCount = collectionView.NumberOfItemsInSection(section); | ||||||||||||||||
if (itemCount > 0) | ||||||||||||||||
{ | ||||||||||||||||
var lastIndexPath = NSIndexPath.FromItemSection(itemCount - 1, section); | ||||||||||||||||
if (Configuration.ScrollDirection == UICollectionViewScrollDirection.Vertical) | ||||||||||||||||
{ | ||||||||||||||||
collectionView.ScrollToItem(lastIndexPath, UICollectionViewScrollPosition.Bottom, true); | ||||||||||||||||
} | ||||||||||||||||
else | ||||||||||||||||
{ | ||||||||||||||||
collectionView.ScrollToItem(lastIndexPath, UICollectionViewScrollPosition.Right, true); | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The scroll position logic assumes left-to-right layout direction. For right-to-left languages or when FlowDirection is RightToLeft, scrolling to UICollectionViewScrollPosition.Right may not provide the expected behavior of showing the 'last' item from the user's perspective.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kubaflo I think this might make sense? |
||||||||||||||||
} | ||||||||||||||||
return; | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
public override CGPoint TargetContentOffset(CGPoint proposedContentOffset, CGPoint scrollingVelocity) | ||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
x:Class="Maui.Controls.Sample.Issues.Issue28716"> | ||
<Grid RowDefinitions="Auto,100,100,100"> | ||
<Button Text="Add Item" | ||
Command="{Binding AddItemCommand}" | ||
AutomationId="AddItemButton" | ||
HorizontalOptions="Center" | ||
VerticalOptions="Center"/> | ||
<CollectionView | ||
ItemsUpdatingScrollMode="KeepLastItemInView" | ||
ItemsSource="{Binding Items}" | ||
Grid.Row="1"> | ||
<CollectionView.ItemsLayout> | ||
<LinearItemsLayout Orientation="Horizontal"/> | ||
</CollectionView.ItemsLayout> | ||
<CollectionView.ItemTemplate> | ||
<DataTemplate> | ||
<Label Text="{Binding . ,StringFormat='{0}cv1'}"/> | ||
</DataTemplate> | ||
</CollectionView.ItemTemplate> | ||
</CollectionView> | ||
<CollectionView | ||
ItemsUpdatingScrollMode="KeepLastItemInView" | ||
FlowDirection="RightToLeft" | ||
ItemsSource="{Binding Items}" | ||
Grid.Row="2"> | ||
<CollectionView.ItemsLayout> | ||
<LinearItemsLayout Orientation="Horizontal"/> | ||
</CollectionView.ItemsLayout> | ||
<CollectionView.ItemTemplate> | ||
<DataTemplate> | ||
<Label Text="{Binding . ,StringFormat='{0}cv2'}"/> | ||
</DataTemplate> | ||
</CollectionView.ItemTemplate> | ||
</CollectionView> | ||
<CollectionView | ||
ItemsUpdatingScrollMode="KeepLastItemInView" | ||
ItemsSource="{Binding Items}" | ||
Grid.Row="3"> | ||
<CollectionView.ItemTemplate> | ||
<DataTemplate> | ||
<Label Text="{Binding . ,StringFormat='{0}cv3'}"/> | ||
</DataTemplate> | ||
</CollectionView.ItemTemplate> | ||
</CollectionView> | ||
</Grid> | ||
</ContentPage> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System.Collections.ObjectModel; | ||
|
||
namespace Maui.Controls.Sample.Issues; | ||
|
||
[Issue(IssueTracker.Github, 28716, "Support for KeepLastItemInView for CV2", PlatformAffected.iOS)] | ||
public partial class Issue28716 : ContentPage | ||
{ | ||
private ObservableCollection<string> _items; | ||
public ObservableCollection<string> Items | ||
{ | ||
get => _items; | ||
set | ||
{ | ||
if (_items != value) | ||
{ | ||
_items = value; | ||
OnPropertyChanged(); | ||
} | ||
} | ||
} | ||
|
||
public Command AddItemCommand => new(() => | ||
{ | ||
Items.Add($"Item{_items.Count}"); | ||
}); | ||
|
||
public Issue28716() | ||
{ | ||
InitializeComponent(); | ||
Items = [.. Enumerable.Range(0, 20).Select(x => $"Item{x}")]; | ||
BindingContext = this; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using NUnit.Framework; | ||
using UITest.Appium; | ||
using UITest.Core; | ||
|
||
namespace Microsoft.Maui.TestCases.Tests.Issues; | ||
public class Issue28716 : _IssuesUITest | ||
{ | ||
public Issue28716(TestDevice testDevice) : base(testDevice) | ||
{ | ||
} | ||
|
||
public override string Issue => "Support for KeepLastItemInView for CV2"; | ||
|
||
[Test] | ||
[Category(UITestCategories.CollectionView)] | ||
public void KeepLastItemInViewShouldWork() | ||
{ | ||
App.WaitForElement("AddItemButton"); | ||
App.Click("AddItemButton"); | ||
App.WaitForElement("Item20cv1"); | ||
App.WaitForElement("Item20cv2"); | ||
App.WaitForElement("Item20cv3"); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.