Skip to content

Fix CarouselView Regression #28955

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

Closed
wants to merge 11 commits into from
Closed

Fix CarouselView Regression #28955

wants to merge 11 commits into from

Conversation

kubaflo
Copy link
Contributor

@kubaflo kubaflo commented Apr 12, 2025

Description

A proposed fix for a minor regression introduced in this pr #28225

Issues Fixed

Fixes #28930

Before After

@albyrock87

@Copilot Copilot AI review requested due to automatic review settings April 12, 2025 18:03
@kubaflo kubaflo requested a review from a team as a code owner April 12, 2025 18:03
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • src/Controls/tests/TestCases.HostApp/Issues/Issue28930.xaml: Language not supported

@@ -104,6 +106,13 @@ public override UICollectionViewLayoutAttributes PreferredLayoutAttributesFittin
var size = ConstrainedSize == default ? Measure() : ConstrainedSize;
_size = size.ToSize();
_needsArrange = true;

if (IsCarouselViewCell && PlatformHandler?.VirtualView is { } virtualView)
Copy link
Preview

Copilot AI Apr 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding an inline comment explaining the rationale for applying a custom measurement override for carousel view cells to aid future maintainers.

Copilot uses AI. Check for mistakes.

@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Apr 12, 2025
@kubaflo kubaflo self-assigned this Apr 12, 2025
@kubaflo kubaflo added platform/ios area-controls-collectionview CollectionView, CarouselView, IndicatorView labels Apr 12, 2025
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@Bamich
Copy link

Bamich commented Apr 13, 2025

Hi! I tried this, it seems ItemsLayout="HorizontalList" is not respected. All CarouselViews in my project become vertical (at least child Grid elements are arranged vertically)

@albyrock87
Copy link
Contributor

@Bamich have you tried this after this merge cfe6e69 ?
If so, may you show a sample code, including whether you're using CV1 or CV2 handler?
Because all UITests pass here, including the one which fixes the issue.
Thanks

@kubaflo kubaflo changed the title Incorrect label LineBreakMode in IOS inside CarouselView - fix Improve iOS CarouselView performance Apr 13, 2025
@Bamich
Copy link

Bamich commented Apr 13, 2025

@albyrock87 @kubaflo I tested it again. CV1 seems to work as expected. But CV2 doesn't - all child elements are arranged vertically.

This is also easily reproduced in the original sample:

  1. Turn on CV2
  2. Run sample
  3. Most importantly - try to scroll the carousel horizontally, and then vertically, you will see that the scroll will go exactly vertically. In statics it is not obvious, you need to start scrolling :)

Direct ItemsLayout="HorizontalList" does not affect the behavior.

scr_rec.mov

You can also replace the MainPage.xml code in the original example with this simpler one, and then scroll the CarouselView in different directions:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
	x:Class="IssueLabeliOS9060.MainPage"
	xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
	xmlns:local="clr-namespace:IssueLabeliOS9060">
	<Grid>
		<Border Padding="10">
			<Grid VerticalOptions="Fill">
				<CarouselView HeightRequest="200" x:Name="MyCarousel" EmptyView="No data">
					<CarouselView.ItemsSource>
						<x:Array Type="{x:Type x:String}">
							<x:String>First</x:String>
							<x:String>Second</x:String>
							<x:String>Third</x:String>
						</x:Array>
					</CarouselView.ItemsSource>
					<CarouselView.ItemTemplate>
						<DataTemplate>
							<Grid
								Padding="20"
								HorizontalOptions="Fill" VerticalOptions="Fill">
								<Label
									Text="{Binding .}"
									HorizontalOptions="Fill" VerticalOptions="Fill"
									BackgroundColor="red" />
							</Grid>
						</DataTemplate>
					</CarouselView.ItemTemplate>
				</CarouselView>
			</Grid>
		</Border>
	</Grid>

</ContentPage>

Copy link
Contributor

@jsuarezruiz jsuarezruiz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some CarouselView UITests failing on iOS/Catalyst:
image

The app was expected to be running still, investigate as possible crash

Could you review if are related with the changes?

@albyrock87
Copy link
Contributor

albyrock87 commented Apr 14, 2025

@jsuarezruiz the fix is ready here kubaflo#4

I really don't understand why, but passing the scroll direction makes the Carousel2 handler crash the app without any message :/
Anyway we don't need that information in the new CarouselTemplatedCell2 🙂
I added a comment in the code and an additional test in Issue28930 to verify the carousel can scroll horizontally.

We can wait for the merge or you can cherry pick and run AZP immediately

@Bamich
Copy link

Bamich commented Apr 14, 2025

I have a proposal. It doesn't seem to be directly related to this PR, but since it's called "Improve iOS CarouselView performance", maybe we should fix this right away? If I set the scroll index for a CarouselView with CV2 on iOS "too early" I get a random exception "cannot access disposed object NSPathIndex". Perhaps we should move the projectedPosition calculation a little lower in CarouselViewController2.cs by changing this part:

<...>

// with Task.Delay(100) below it results in "cannot access disposed object NSPathIndex"
var projectedPosition = NSIndexPath.FromItemSection(position, _section);

if (LoopItemsSource.Loop)
{
	//We need to set the position to the correct position since we added 1 item at the beginning
	projectedPosition = GetScrollToIndexPath(position);
}

var uICollectionViewScrollPosition = IsHorizontal ? UICollectionViewScrollPosition.CenteredHorizontally :
	UICollectionViewScrollPosition.CenteredVertically;

await Task.Delay(100).ContinueWith((t) =>
{
	MainThread.BeginInvokeOnMainThread(() =>
	{
		if (!IsViewLoaded)
		{
			return;
		}
		InitialPositionSet = true;

		if (ItemsSource is null || ItemsSource.ItemCount == 0)
		{
			return;
		}

		CollectionView.ScrollToItem(projectedPosition, uICollectionViewScrollPosition, false);

		//Set the position on VirtualView to update the CurrentItem also
		SetPosition(position);

		UpdateVisualStates();
	});

});

<...>

to something like this?

<...>

var uICollectionViewScrollPosition = IsHorizontal ? UICollectionViewScrollPosition.CenteredHorizontally : 
	UICollectionViewScrollPosition.CenteredVertically;

// just wait..
await Task.Delay(100);

MainThread.BeginInvokeOnMainThread(() =>
{
	if (!IsViewLoaded)
	{
		return;
	}

	InitialPositionSet = true;

	if (ItemsSource is null || ItemsSource.ItemCount == 0)
	{
		return;
	}

	// Get projectedPosition immediately before use (no crash)
	NSIndexPath projectedPosition = LoopItemsSource.Loop
		? GetScrollToIndexPath(position)
		: NSIndexPath.FromItemSection(position, _section);

	CollectionView.ScrollToItem(projectedPosition, uICollectionViewScrollPosition, false);

		//Set the position on VirtualView to update the CurrentItem also
		SetPosition(position);

		UpdateVisualStates();
});

<...>

Thanks

@albyrock87
Copy link
Contributor

@Bamich I think that's reasonable.
In the mean time, may you verify if the last commit here fixes the issue?
I believe so given I also added a check in the UITest, but if you can confirm I'd appreciate.
Thanks

@PureWeen
Copy link
Member

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@Bamich
Copy link

Bamich commented Apr 14, 2025

@albyrock87 Yes, I checked and confirm that the last commit fixes the scroll orientation issue

@PureWeen PureWeen changed the title Improve iOS CarouselView performance Fix CarouselView Regression Apr 14, 2025
@PureWeen PureWeen added this to the .NET 9 SR6.1 milestone Apr 14, 2025
@jsuarezruiz
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@@ -799,7 +814,8 @@ public static bool WaitForTextToBePresentInElement(this IApp app, string automat
while (true)
{
var element = app.FindElements(automationId).FirstOrDefault();
if (element != null && (element.GetText()?.Contains(text, StringComparison.OrdinalIgnoreCase) ?? false))

if (element != null && element.TryGetText(out var s) && s.Contains(text, StringComparison.OrdinalIgnoreCase))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need to touch the PR again, this can be:

Suggested change
if (element != null && element.TryGetText(out var s) && s.Contains(text, StringComparison.OrdinalIgnoreCase))
if (element is not null && element.TryGetText(out var s) && s.Contains(text, StringComparison.OrdinalIgnoreCase))

@rmarinho
Copy link
Member

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@@ -61,6 +61,7 @@ public void UpdateLayout(UICollectionViewLayout newLayout)

if (newLayout is UICollectionViewCompositionalLayout compositionalLayout)
{
// Note: on carousel layout, the scroll direction is always vertical
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don t understand this comment, can t we have a CarouselView for scroll left and right or up and down? So 2 directions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I'm saying is that this variable on carousel is always set to vertical, even when having a horizontal carousel.
Horizontal carousel is made in a different way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do it in this way (always set to vertical) to achieve horizontal paging with snapping (GroupPagingCentered). Thanks to it, we can use OrthogonalScrollingBehavior.GroupPagingCentered to scroll the section horizontally. And even if CarouselView is vertically oriented, each section scrolls horizontally — which results in the carousel-style behavior

@dotnet dotnet deleted a comment from PureWeen Apr 15, 2025
@@ -521,6 +521,11 @@ protected virtual string DetermineCellReuseId(NSIndexPath indexPath)
: VerticalDefaultCell.ReuseId;
}

private protected virtual (Type CellType, string CellTypeReuseId) DetermineTemplatedCellType()
{
return (ItemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Vertical ? typeof(VerticalCell) : typeof(HorizontalCell), "maui");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this use the ReuseID ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't using it, so I kept the old reuseId creation logic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh this was just reverted to what it was?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm referring to line 508 above

var reuseId = $"_maui_{cellOrientation}_{dataTemplate.Id}";

It was using this maui string so I simply kept it like that.
While CV2 is a different story: there it was using the reuseId so I used it.

rmarinho
rmarinho previously approved these changes Apr 16, 2025
@rmarinho rmarinho moved this from Todo to Approved in MAUI SDK Ongoing Apr 16, 2025
@rmarinho rmarinho changed the base branch from main to release/9.0.1xx-sr6 April 16, 2025 11:29
@rmarinho rmarinho dismissed their stale review April 16, 2025 11:29

The base branch was changed.

@rmarinho
Copy link
Member

/rebase

PureWeen and others added 10 commits April 16, 2025 11:31
…50407.3 (dotnet#28892)

Microsoft.DotNet.XHarness.CLI , Microsoft.DotNet.XHarness.TestRunners.Common , Microsoft.DotNet.XHarness.TestRunners.Xunit
 From Version 9.0.0-prerelease.25203.2 -> To Version 9.0.0-prerelease.25207.3

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
* [ai]Add release notes prompt

* Improve prompt release notes

* [ai] improve prompt

* [ai] Try map commits to github users

* Small update to get correct github user

* [ai] Add list of common users

* [ai] Remove extra vriable
@rmarinho rmarinho changed the base branch from release/9.0.1xx-sr6 to main April 16, 2025 16:18
@rmarinho
Copy link
Member

/rebase

@kubaflo
Copy link
Contributor Author

kubaflo commented Apr 16, 2025

Closed in favour of #29035

@kubaflo kubaflo closed this Apr 16, 2025
@github-project-automation github-project-automation bot moved this from Approved to Done in MAUI SDK Ongoing Apr 16, 2025
@github-actions github-actions bot locked and limited conversation to collaborators May 17, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-controls-collectionview CollectionView, CarouselView, IndicatorView community ✨ Community Contribution platform/ios
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Incorrect label LineBreakMode in IOS inside CarouselView
8 participants