Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
34 changes: 26 additions & 8 deletions src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,7 @@ protected bool IsIndexPathValid(NSIndexPath indexPath)
public override Size GetDesiredSize(double widthConstraint, double heightConstraint)
{
var contentSize = Controller.GetSize();

// If contentSize comes back null, it means none of the content has been realized yet;
// we need to return the expansive size the collection view wants by default to get
// it to start measuring its content
if (contentSize.Height == 0 || contentSize.Width == 0)
{
return base.GetDesiredSize(widthConstraint, heightConstraint);
}
contentSize = EnsureContentSizeForScrollDirection(widthConstraint, heightConstraint, contentSize);

// Our target size is the smaller of it and the constraints
var width = contentSize.Width <= widthConstraint ? contentSize.Width : widthConstraint;
Expand All @@ -206,5 +199,30 @@ public override Size GetDesiredSize(double widthConstraint, double heightConstra

return new Size(width, height);
}

Size EnsureContentSizeForScrollDirection(double widthConstraint, double heightConstraint, Size contentSize)
{
// Get the CollectionView orientation
var scrollDirection = Controller.GetScrollDirection();

// If contentSize is zero in the relevant dimension (height for vertical, width for horizontal),
// it means none of the content has been realized yet; we need to return the expansive size
// the collection view wants by default to get it to start measuring its content
if ((scrollDirection == UICollectionViewScrollDirection.Vertical && contentSize.Height == 0) ||
(scrollDirection == UICollectionViewScrollDirection.Horizontal && contentSize.Width == 0))
{
var desiredSize = base.GetDesiredSize(widthConstraint, heightConstraint);
if (scrollDirection == UICollectionViewScrollDirection.Vertical)
{
contentSize.Height = desiredSize.Height;
}
else
{
contentSize.Width = desiredSize.Width;
}
}

return contentSize;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,11 @@ internal Size GetSize()
return CollectionView.CollectionViewLayout.CollectionViewContentSize.ToSize();
}

internal UICollectionViewScrollDirection GetScrollDirection()
{
return ScrollDirection;
}

internal void UpdateView(object view, DataTemplate viewTemplate, ref UIView uiView, ref VisualElement formsElement)
{
// Is view set on the ItemsView?
Expand Down
176 changes: 176 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue31897.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using System.ComponentModel;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 31897, "CollectionView card height appears larger in Developer Balance sample", PlatformAffected.iOS | PlatformAffected.macOS)]
public class Issue31897 : ContentPage
{
Button getHeight;
Label HeightLabel;
CollectionView2 ProjectsCollectionView;

public Issue31897()
{
var mainGrid = new Grid();

var scrollView = new ScrollView();

// Create the inner grid with padding and row spacing
var innerGrid = new Grid
{
Padding = new Thickness(10),
RowSpacing = 10
};

// Add row definitions
innerGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
innerGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
innerGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

// Create the "Get CV Height" button
getHeight = new Button
{
Text = "Get CV Height",
AutomationId = "GetHeightButton"
};

getHeight.Clicked += (object sender, EventArgs e) =>
{
HeightLabel.Text = Math.Round(ProjectsCollectionView.Height).ToString();
};

getHeight.SetValue(SemanticProperties.HeadingLevelProperty, SemanticHeadingLevel.Level1);
Grid.SetRow(getHeight, 0);

// Create the height label
HeightLabel = new Label();
HeightLabel.AutomationId = "HeightLabel";
HeightLabel.SetValue(SemanticProperties.HeadingLevelProperty, SemanticHeadingLevel.Level1);
Grid.SetRow(HeightLabel, 1);

// Create the CollectionView
ProjectsCollectionView = new CollectionView2
{
Margin = new Thickness(-7.5, 0),
MinimumHeightRequest = 250,
SelectionMode = SelectionMode.Single
};

// Set up the ItemsLayout
ProjectsCollectionView.ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal)
{
ItemSpacing = 7.5
};

// Create the DataTemplate for items
var itemTemplate = new DataTemplate(() =>
{
var border = new Border
{
WidthRequest = 200,
Background = Colors.GreenYellow
};

var contentView = new ContentView();

var verticalStackLayout = new VerticalStackLayout
{
Spacing = 15,
Background = Colors.Red,
VerticalOptions = LayoutOptions.Start
};

// Name label
var nameLabel = new Label
{
TextColor = Colors.Gray,
FontSize = 14,
TextTransform = TextTransform.Uppercase
};
nameLabel.SetBinding(Label.TextProperty, "Name");

// Description label
var descriptionLabel = new Label
{
LineBreakMode = LineBreakMode.WordWrap
};
descriptionLabel.SetBinding(Label.TextProperty, "Description");

verticalStackLayout.Children.Add(nameLabel);
verticalStackLayout.Children.Add(descriptionLabel);
contentView.Content = verticalStackLayout;
border.Content = contentView;

return border;
});

ProjectsCollectionView.ItemTemplate = itemTemplate;

// Set up data binding
var viewModel = new Issue31897ViewModel();
BindingContext = viewModel;
ProjectsCollectionView.SetBinding(CollectionView.ItemsSourceProperty, "Projects");

Grid.SetRow(ProjectsCollectionView, 2);

// Add all controls to the inner grid
innerGrid.Children.Add(getHeight);
innerGrid.Children.Add(HeightLabel);
innerGrid.Children.Add(ProjectsCollectionView);

// Set up the hierarchy
scrollView.Content = innerGrid;
mainGrid.Children.Add(scrollView);
Content = mainGrid;
}

public class Issue31897ViewModel : INotifyPropertyChanged
{
List<Issue31897Project> _projects = new();

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public List<Issue31897Project> Projects
{
get { return _projects; }
set
{
_projects = value;
OnPropertyChanged(nameof(Projects));
}
}

public Issue31897ViewModel()
{
LoadData();
}

void LoadData()
{
var projects = new List<Issue31897Project>();
for (int i = 0; i < 4; i++)
{
projects.Add(new Issue31897Project
{
ID = i,
Name = "Developer balance card view " + (i + 1),
Description = "This is a sample description for project " + (i + 1)
});
}

Projects = projects;
}
}

public class Issue31897Project
{
public int ID { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue31897 : _IssuesUITest
{
public Issue31897(TestDevice testDevice) : base(testDevice)
{
}
public override string Issue => "CollectionView card height appears larger in Developer Balance sample";

[Test]
[Category(UITestCategories.CollectionView)]
public void EnsureCollectionViewLayoutOnItemsSourceChange()
Copy link
Contributor

Choose a reason for hiding this comment

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

Could expand the tests by setting different item sources and asserting layout change (e.g., when items are added/removed, does the height still reflect correctly?). This ensures future changes do not regress measurement logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jsuarezruiz, New test scenarios were included. Could you please review and let me know if any concerns?

{
App.WaitForElement("GetHeightButton");
App.Tap("GetHeightButton");
var label = App.WaitForElement("HeightLabel");
Assert.That(label.GetText(), Is.EqualTo("250"));
}
}
Loading