Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/Controls/src/Core/Handlers/Items2/iOS/LayoutFactory2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ class CustomUICollectionViewCompositionalLayout : UICollectionViewCompositionalL
ItemsLayout? _itemsLayout;
LayoutGroupingInfo? _groupingInfo;
LayoutHeaderFooterInfo? _headerFooterInfo;
CGSize _currentSize;

public CustomUICollectionViewCompositionalLayout(LayoutSnapInfo snapInfo, LayoutGroupingInfo? groupingInfo, LayoutHeaderFooterInfo? headerFooterInfo, UICollectionViewCompositionalLayoutSectionProvider sectionProvider, UICollectionViewCompositionalLayoutConfiguration configuration, ItemsLayout? itemsLayout) : base(sectionProvider, configuration)
{
Expand Down Expand Up @@ -555,6 +556,20 @@ void ForceScrollToLastItem(UICollectionView collectionView)
}
}

public override bool ShouldInvalidateLayoutForBoundsChange(CGRect newBounds)
{
// If the size hasn't changed, use the base implementation
if (newBounds.Size.IsCloseTo(_currentSize))
{
return base.ShouldInvalidateLayoutForBoundsChange(newBounds);
}

// Size has changed (e.g., rotation), so we need to invalidate the layout
// to ensure cells are properly measured and displayed
_currentSize = newBounds.Size;
return true;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[major] CollectionView iOS/MacCatalyst — Returning true invalidates the compositional layout, but it leaves CollectionViewHandler2’s MeasureFirstItem cache intact. TemplatedCell2 reuses that cached first-item size after a bounds change, so rotations or split-view resizes can still lay out cells with the pre-rotation measurement for the default MeasureFirstItem strategy. Please clear the cached first-item size when the bounds size changes before invalidating the layout.

}

public override CGPoint TargetContentOffset(CGPoint proposedContentOffset, CGPoint scrollingVelocity)
{
var snapPointsType = _snapInfo.SnapType;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 74 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue32435.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Microsoft.Maui.Controls;
using System.Collections.ObjectModel;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 32435, "Rotating the Simulator causes the text on the collection view to disappear", PlatformAffected.iOS)]

public class Issue32435 : ContentPage
{
readonly ObservableCollection<string> items = new();
readonly CollectionView2 collectionView;

public Issue32435()
{
var rootGrid = new Grid
{
Margin = 20,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Star }
}
};

var topStack = new StackLayout();
topStack.Add(new Label { Text = "CollectionView text should appear after rotating the device", AutomationId = "InstructionLabel" });
rootGrid.Add(topStack);
Grid.SetRow(topStack, 0);

var addButton = new Button
{
Text = "Add",
AutomationId = "AddButton",
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start
};
addButton.Clicked += ButtonAdd_Clicked;
rootGrid.Add(addButton);
Grid.SetRow(addButton, 1);

var innerGrid = new Grid
{
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Star }
}
};

collectionView = new CollectionView2
{
BackgroundColor = Colors.LightSalmon,
HeightRequest = 50,
ItemsLayout = new GridItemsLayout(ItemsLayoutOrientation.Horizontal)
};

innerGrid.Add(collectionView);
Grid.SetRow(collectionView, 0);

rootGrid.Add(innerGrid);
Grid.SetRow(innerGrid, 2);

items.Add("item: " + items.Count);
collectionView.ItemsSource = items;

Content = rootGrid;
}

void ButtonAdd_Clicked(object sender, System.EventArgs e)
{
items.Add("item: " + items.Count);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#if TEST_FAILS_ON_CATALYST && TEST_FAILS_ON_WINDOWS //Issue reproduce only when rotating device.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[major] Regression Prevention — This conditional includes the test on Android as well as iOS, but the production fix is only in Items2/iOS/LayoutFactory2.cs and the issue is iOS-only. On Android this test will pass with or without the fix, so Android gate verification cannot prove the regression and can produce the observed “tests did not behave as expected” result. Scope this regression test to #if IOS (or otherwise only run it on a platform covered by the fix) and remove the Android snapshot if Android behavior is not intentionally being validated.

Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[moderate] Regression Prevention — This guard also compiles the test into Android because the Android test project defines both TEST_FAILS_ON_CATALYST and TEST_FAILS_ON_WINDOWS, even though the product fix is iOS Items2-only and the issue is marked iOS. That adds an Android screenshot baseline for an unrelated platform and can fail on Android rendering/orientation differences which this PR does not change; scope the regression to #if IOS (or otherwise document why Android coverage is intentional).

using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue32435 : _IssuesUITest
{
public Issue32435(TestDevice device) : base(device) { }

public override string Issue => "Rotating the Simulator causes the text on the collection view to disappear";
[Test]
[Category(UITestCategories.CollectionView)]
public void VerifyCollectionViewTextShouldAppearAfterRotatingTheDevice()
{
App.WaitForElement("InstructionLabel");
App.Tap("AddButton");
App.SetOrientationLandscape();
App.SetOrientationPortrait();
VerifyScreenshot();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[high] Regression test is missing the baseline used by the iOS gate

The supplied iOS gate failed on this line because VerifyScreenshot() looked for src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyCollectionViewTextShouldAppearAfterRotatingTheDevice.png, but the PR only adds the snapshots/ios/ baseline. Add the generated ios-26 baseline as well; otherwise the new regression test fails even if the UI renders correctly.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[moderate] Regression test may capture before post-rotation layout settles

VerifyScreenshot() is called immediately after App.SetOrientationPortrait() with no post-rotation wait and no explicit retry timeout. Orientation changes can complete before the final layout/screenshot state is stable on device.

Please re-wait for a stable element or use a screenshot retry timeout after returning to portrait, for example App.WaitForElement("InstructionLabel"); VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(3));.

Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[moderate] Regression Prevention — The screenshot is captured immediately after two orientation changes. SetOrientationPortrait() only sends the command; it does not wait for the rotation/layout pass to settle, and nearby rotation screenshot tests use VerifyScreenshot(..., retryTimeout: TimeSpan.FromSeconds(2)) for this reason. Without a retry this regression test can snapshot mid-rotation and become flaky on slower iOS simulators.

}
}
#endif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading