-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[iOS, macOS] Fixed CollectionView group header size changes with ItemSizingStrategy #33161
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?
Conversation
035294e to
d9ac00e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a CollectionView issue on iOS and macOS where group headers and footers incorrectly inherit the size of the first measured item when the ItemSizingStrategy is set to MeasureFirstItem. The fix introduces a boolean flag isSupplementaryView to distinguish supplementary views (headers/footers) from regular item cells, ensuring headers/footers are always measured directly and never use the cached first-item measurement.
Key Changes:
- Added flag to prevent headers/footers from using the first-item measurement cache
- Updated measurement logic to conditionally apply cached measurements only to item cells
- Added comprehensive UI test to verify the fix works correctly
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/Controls/src/Core/Handlers/Items2/iOS/TemplatedCell2.cs |
Adds isSupplementaryView flag and updates measurement logic to skip cached measurements for headers/footers |
src/Controls/src/Core/Handlers/Items2/iOS/StructuredItemsViewController2.cs |
Sets the isSupplementaryView flag when configuring supplementary views |
src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs |
Explicitly clears the isSupplementaryView flag for regular item cells |
src/Controls/src/Core/Handlers/Items2/iOS/GroupableItemsViewController2.cs |
Sets the isSupplementaryView flag for group headers and footers |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33130.cs |
NUnit test that verifies header height remains constant when ItemSizingStrategy changes |
src/Controls/tests/TestCases.HostApp/Issues/Issue33130.xaml |
XAML test page with grouped CollectionView and button to switch sizing strategy |
src/Controls/tests/TestCases.HostApp/Issues/Issue33130.xaml.cs |
Code-behind with view models demonstrating the group header sizing issue |
| Size _cachedConstraints; | ||
|
|
||
| // Indicates the cell is being used as a supplementary view (group header/footer) | ||
| internal bool isSupplementaryView = false; |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The field isSupplementaryView does not follow C# naming conventions. Internal fields should use PascalCase with an underscore prefix (e.g., _isSupplementaryView) to be consistent with other fields in this class like _measureInvalidated, _needsArrange, _measuredSize, and _cachedConstraints.
| } | ||
| } | ||
|
|
||
| public class GroupHeaderTestAnimalGroup : ObservableCollection<GroupHeaderTestAnimal> |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class name GroupHeaderTestAnimalGroup is verbose and redundant. Following the naming pattern used in other test files (e.g., Issue12374Model), this should be named AnimalGroup or Issue33130AnimalGroup to be more concise while maintaining clarity.
| public ObservableCollection<GroupHeaderTestAnimalGroup> Animals { get; set; } | ||
|
|
||
| public GroupedAnimalsViewModel() | ||
| { | ||
| Animals = new ObservableCollection<GroupHeaderTestAnimalGroup> | ||
| { | ||
| new GroupHeaderTestAnimalGroup("Bears") | ||
| { | ||
| new GroupHeaderTestAnimal { Name = "Grizzly Bear", Location = "North America", ImageUrl = "bear.jpg" }, | ||
| new GroupHeaderTestAnimal { Name = "Polar Bear", Location = "Arctic", ImageUrl = "bear.jpg" }, | ||
| }, | ||
| new GroupHeaderTestAnimalGroup("Monkeys") | ||
| { | ||
| new GroupHeaderTestAnimal { Name = "Baboon", Location = "Africa", ImageUrl = "monkey.jpg" }, | ||
| new GroupHeaderTestAnimal { Name = "Capuchin Monkey", Location = "South America", ImageUrl = "monkey.jpg" }, | ||
| new GroupHeaderTestAnimal { Name = "Spider Monkey", Location = "Central America", ImageUrl = "monkey.jpg" }, | ||
| }, | ||
| new GroupHeaderTestAnimalGroup("Elephants") | ||
| { | ||
| new GroupHeaderTestAnimal { Name = "African Elephant", Location = "Africa", ImageUrl = "elephant.jpg" }, | ||
| new GroupHeaderTestAnimal { Name = "Asian Elephant", Location = "Asia", ImageUrl = "elephant.jpg" }, | ||
| } | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| public class GroupHeaderTestAnimalGroup : ObservableCollection<GroupHeaderTestAnimal> | ||
| { | ||
| public string Name { get; set; } | ||
|
|
||
| public GroupHeaderTestAnimalGroup(string name) : base() | ||
| { | ||
| Name = name; | ||
| } | ||
| } | ||
|
|
||
| public class GroupHeaderTestAnimal |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class name GroupHeaderTestAnimal is verbose with redundant "GroupHeaderTest" prefix. Following the naming pattern used in other test files (e.g., Issue12374Model), this should be named Animal or Issue33130Animal to be more concise while maintaining clarity.
| public ObservableCollection<GroupHeaderTestAnimalGroup> Animals { get; set; } | |
| public GroupedAnimalsViewModel() | |
| { | |
| Animals = new ObservableCollection<GroupHeaderTestAnimalGroup> | |
| { | |
| new GroupHeaderTestAnimalGroup("Bears") | |
| { | |
| new GroupHeaderTestAnimal { Name = "Grizzly Bear", Location = "North America", ImageUrl = "bear.jpg" }, | |
| new GroupHeaderTestAnimal { Name = "Polar Bear", Location = "Arctic", ImageUrl = "bear.jpg" }, | |
| }, | |
| new GroupHeaderTestAnimalGroup("Monkeys") | |
| { | |
| new GroupHeaderTestAnimal { Name = "Baboon", Location = "Africa", ImageUrl = "monkey.jpg" }, | |
| new GroupHeaderTestAnimal { Name = "Capuchin Monkey", Location = "South America", ImageUrl = "monkey.jpg" }, | |
| new GroupHeaderTestAnimal { Name = "Spider Monkey", Location = "Central America", ImageUrl = "monkey.jpg" }, | |
| }, | |
| new GroupHeaderTestAnimalGroup("Elephants") | |
| { | |
| new GroupHeaderTestAnimal { Name = "African Elephant", Location = "Africa", ImageUrl = "elephant.jpg" }, | |
| new GroupHeaderTestAnimal { Name = "Asian Elephant", Location = "Asia", ImageUrl = "elephant.jpg" }, | |
| } | |
| }; | |
| } | |
| } | |
| public class GroupHeaderTestAnimalGroup : ObservableCollection<GroupHeaderTestAnimal> | |
| { | |
| public string Name { get; set; } | |
| public GroupHeaderTestAnimalGroup(string name) : base() | |
| { | |
| Name = name; | |
| } | |
| } | |
| public class GroupHeaderTestAnimal | |
| public ObservableCollection<Issue33130AnimalGroup> Animals { get; set; } | |
| public GroupedAnimalsViewModel() | |
| { | |
| Animals = new ObservableCollection<Issue33130AnimalGroup> | |
| { | |
| new Issue33130AnimalGroup("Bears") | |
| { | |
| new Issue33130Animal { Name = "Grizzly Bear", Location = "North America", ImageUrl = "bear.jpg" }, | |
| new Issue33130Animal { Name = "Polar Bear", Location = "Arctic", ImageUrl = "bear.jpg" }, | |
| }, | |
| new Issue33130AnimalGroup("Monkeys") | |
| { | |
| new Issue33130Animal { Name = "Baboon", Location = "Africa", ImageUrl = "monkey.jpg" }, | |
| new Issue33130Animal { Name = "Capuchin Monkey", Location = "South America", ImageUrl = "monkey.jpg" }, | |
| new Issue33130Animal { Name = "Spider Monkey", Location = "Central America", ImageUrl = "monkey.jpg" }, | |
| }, | |
| new Issue33130AnimalGroup("Elephants") | |
| { | |
| new Issue33130Animal { Name = "African Elephant", Location = "Africa", ImageUrl = "elephant.jpg" }, | |
| new Issue33130Animal { Name = "Asian Elephant", Location = "Asia", ImageUrl = "elephant.jpg" }, | |
| } | |
| }; | |
| } | |
| } | |
| public class Issue33130AnimalGroup : ObservableCollection<Issue33130Animal> | |
| { | |
| public string Name { get; set; } | |
| public Issue33130AnimalGroup(string name) : base() | |
| { | |
| Name = name; | |
| } | |
| } | |
| public class Issue33130Animal |
| var heightDifference = Math.Abs(headerRectBefore.Height - headerRectAfter.Height); | ||
|
|
||
| // Assert that the height difference is minimal (less than 5 pixels tolerance) | ||
| Assert.That(heightDifference, Is.EqualTo(0), |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assertion uses Is.EqualTo(0) which expects the height difference to be exactly zero. However, the comment on line 37 mentions "Allow for small rounding differences". This is inconsistent. If you want to allow for small rounding differences, the assertion should use a tolerance (e.g., Is.EqualTo(0).Within(1) or Is.LessThanOrEqualTo(1)) instead of requiring exact equality. As written, the test may fail due to minor floating-point precision differences across platforms.
| Assert.That(heightDifference, Is.EqualTo(0), | |
| Assert.That(heightDifference, Is.EqualTo(0).Within(5), |
| { | ||
| TemplatedCell2.ScrollDirection = ScrollDirection; | ||
|
|
||
| // Ensure this cell is treated as a regular item cell (not a supplementary view) |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment "Ensure this cell is treated as a regular item cell (not a supplementary view)" is helpful, but it would be more informative to explain why this is necessary. Consider expanding it to: "Ensure this cell is treated as a regular item cell (not a supplementary view) so it can use the cached first-item measurement for MeasureFirstItem strategy" to provide better context about the fix's purpose.
| // Ensure this cell is treated as a regular item cell (not a supplementary view) | |
| // Ensure this cell is treated as a regular item cell (not a supplementary view) | |
| // so it can use the cached first-item measurement for MeasureFirstItem strategy |
|
/backport to release/10.0.1xx-sr2 |
|
Started backporting to |
| Text="ItemSizingStrategy: MeasureAllItems"/> | ||
| </StackLayout> | ||
|
|
||
| <local:CollectionView2 Grid.Row="1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set this to just CollectionView if this is passing on both CV1 and CV2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set this to just CollectionView if this is passing on both CV1 and CV2
Hi @PureWeen ,
I have changed CollectionView2 to CollectionView to ensure the test case passes on both CV1 and CV2.
… size changes with ItemSizingStrategy (#33166) Backport of #33161 to release/10.0.1xx-sr2 /cc @PureWeen @NanthiniMahalingam --------- Co-authored-by: NanthiniMahalingam <[email protected]>
Root cause
Description of changes
Issues Fixed
Fixes #33130
Validated the behaviour in the following platforms
Output
iOS
issue33130_iOS_before.mov
Issue33130_iOS_after.mov
macOS
issue33130_mac_before.mov
Issue33130_mac_after.mov