[iOS, macOS] Fixed CollectionView group header size changes with ItemSizingStrategy#33161
Conversation
035294e to
d9ac00e
Compare
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
Set this to just CollectionView if this is passing on both CV1 and CV2
There was a problem hiding this comment.
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 <105482474+NanthiniMahalingam@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
…SizingStrategy (#33161) <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root cause - When the ItemSizingStrategy is set to measure the first item, the collection view’s grouped item header or footer template size is taken from the first item’s size instead of the actual size of the grouped item header or footer template. ### Description of changes - I have ignored the first measured item’s size when applying it to the grouped header or footer template, the collection view header or footer view, and the collection view header or footer template in PreferredLayoutAttributesFittingAttributes of TemplatedCell2. ### Issues Fixed Fixes #33130 Validated the behaviour in the following platforms - [x] Android - [x] Windows , - [x] iOS, - [x] MacOS ### Output **iOS** |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/deb143a4-5df0-463d-915c-16254bad658c" >| <video src="https://github.com/user-attachments/assets/56ba82de-f6d9-4b05-8ea7-2a12031282eb">| **macOS** |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/094b3e91-8198-483f-8b87-f3f5777e48b4" >| <video src="https://github.com/user-attachments/assets/5937e5c3-feb5-4138-8c47-9f7a31095a16">|
…SizingStrategy (#33161) <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root cause - When the ItemSizingStrategy is set to measure the first item, the collection view’s grouped item header or footer template size is taken from the first item’s size instead of the actual size of the grouped item header or footer template. ### Description of changes - I have ignored the first measured item’s size when applying it to the grouped header or footer template, the collection view header or footer view, and the collection view header or footer template in PreferredLayoutAttributesFittingAttributes of TemplatedCell2. ### Issues Fixed Fixes #33130 Validated the behaviour in the following platforms - [x] Android - [x] Windows , - [x] iOS, - [x] MacOS ### Output **iOS** |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/deb143a4-5df0-463d-915c-16254bad658c" >| <video src="https://github.com/user-attachments/assets/56ba82de-f6d9-4b05-8ea7-2a12031282eb">| **macOS** |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/094b3e91-8198-483f-8b87-f3f5777e48b4" >| <video src="https://github.com/user-attachments/assets/5937e5c3-feb5-4138-8c47-9f7a31095a16">|
## CollectionView - Fixed the NRE in CarouselViewController on iOS 15.5 & 16.4 by @Ahamed-Ali in #30838 <details> <summary>🔧 Fixes</summary> - [NRE in CarouselViewController on iOS 15.5 & 16.4](#28557) </details> - [iOS, macOS] Fixed CollectionView group header size changes with ItemSizingStrategy by @NanthiniMahalingam in #33161 <details> <summary>🔧 Fixes</summary> - [[NET 10] I6_Grouping - Grouping_with_variable_sized_items changing the 'ItemSizingStrategy' also changes the header size.](#33130) </details> ## Flyout - Add unit tests for TabBar and FlyoutItem navigation ApplyQueryAttributes (#25663) by @StephaneDelcroix in #33006 ## Flyoutpage - Fixed the FlyoutPage.Flyout Disappearing When Maximizing the Window on Mac Platform by @NanthiniMahalingam in #26701 <details> <summary>🔧 Fixes</summary> - [FlyoutPage.Flyout - navigation corrupted when running om mac , on window ok](#22719) </details> ## Mediapicker - [Windows] Fix for PickPhotosAsync throws exception if image is modified by @HarishwaranVijayakumar in #32952 <details> <summary>🔧 Fixes</summary> - [PickPhotosAsync throws exception if image is modified.](#32408) </details> ## Navigation - Fix for TabBar Navigation does not invoke its IQueryAttributable.ApplyQueryAttributes(query) by @SuthiYuvaraj in #25663 <details> <summary>🔧 Fixes</summary> - [Tabs defined in AppShell.xaml does not invoke its view model's IQueryAttributable.ApplyQueryAttributes(query) implementaion](#13537) - [`ShellContent` routes do not call `ApplyQueryAttributes`](#28453) </details> ## ScrollView - Fix ScrollToPosition.Center behavior in ScrollView on iOS and MacCatalyst by @devanathan-vaithiyanathan in #26825 <details> <summary>🔧 Fixes</summary> - [ScrollToPosition.Center Centers the First Item too in iOS and Catalyst](#26760) - [On iOS - ScrollView.ScrollToAsync Element, ScrollToPosition.MakeVisible shifts view to the right, instead of just scrolling vertically](#28965) </details> ## Searchbar - [iOS, Mac, Windows] Fixed CharacterSpacing for SearchBar text and placeholder text by @Dhivya-SF4094 in #30407 <details> <summary>🔧 Fixes</summary> - [[iOS, Mac, Windows] SearchBar CharacterSpacing property is not working as expected](#30366) </details> ## Shell - Update logic for large title display mode on iOS - shell by @kubaflo in #33039 ## TitleView - [iOS] Fixed memory leak with PopToRootAsync when using TitleView by @Vignesh-SF3580 in #28547 <details> <summary>🔧 Fixes</summary> - [NavigationPage.TitleView causes memory leak with PopToRootAsync](#28201) </details> ## Xaml - [C] Fix binding to interface-inherited properties like IReadOnlyList<T>.Count by @StephaneDelcroix in #32912 <details> <summary>🔧 Fixes</summary> - [Compiled Binding to Array.Count provides no result](#13872) </details> - Fix #31939: CommandParameter TemplateBinding lost during reparenting by @StephaneDelcroix in #32961 <details> <summary>🔧 Fixes</summary> - [CommandParameter TemplateBinding Lost During ControlTemplate Reparenting](#31939) </details> <details> <summary>🧪 Testing (4)</summary> - [Testing] Fixed Test case failure in PR 33185 - [12/22/2025] Candidate by @TamilarasanSF4853 in #33257 - [Testing] Re-saved ShouldFlyoutBeVisibleAfterMaximizingWindow test case images in PR 33185 - [12/22/2025] Candidate by @TamilarasanSF4853 in #33271 - [Testing] Fixed Test case failure in PR 33185 - [12/22/2025] Candidate - 2 by @TamilarasanSF4853 in #33299 - [Testing] Fixed Test case failure in PR 33185 - [12/22/2025] Candidate - 3 by @TamilarasanSF4853 in #33311 </details> <details> <summary>📦 Other (2)</summary> - [XSG][BindingSourceGen] Add support for RelayCommand to compiled bindings by @simonrozsival via @Copilot in #32954 <details> <summary>🔧 Fixes</summary> - [Issue #25818](#25818) </details> - Revert "Update logic for large title display mode on iOS - shell (#33039)" in cff7f35 </details> **Full Changelog**: main...inflight/candidate
…SizingStrategy (#33161) <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root cause - When the ItemSizingStrategy is set to measure the first item, the collection view’s grouped item header or footer template size is taken from the first item’s size instead of the actual size of the grouped item header or footer template. ### Description of changes - I have ignored the first measured item’s size when applying it to the grouped header or footer template, the collection view header or footer view, and the collection view header or footer template in PreferredLayoutAttributesFittingAttributes of TemplatedCell2. ### Issues Fixed Fixes #33130 Validated the behaviour in the following platforms - [x] Android - [x] Windows , - [x] iOS, - [x] MacOS ### Output **iOS** |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/deb143a4-5df0-463d-915c-16254bad658c" >| <video src="https://github.com/user-attachments/assets/56ba82de-f6d9-4b05-8ea7-2a12031282eb">| **macOS** |Before|After| |--|--| | <video src="https://github.com/user-attachments/assets/094b3e91-8198-483f-8b87-f3f5777e48b4" >| <video src="https://github.com/user-attachments/assets/5937e5c3-feb5-4138-8c47-9f7a31095a16">|
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [Microsoft.Maui.Controls](https://github.com/dotnet/maui) | nuget | patch | `10.0.20` -> `10.0.30` | --- ### Release Notes <details> <summary>dotnet/maui (Microsoft.Maui.Controls)</summary> ### [`v10.0.30`](https://github.com/dotnet/maui/releases/tag/10.0.30): SR3 [Compare Source](dotnet/maui@10.0.20...10.0.30) #### What's Changed .NET MAUI 10.0.30 introduces significant improvements across all platforms with focus on quality, performance, and developer experience. This release includes 106 commits with various improvements, bug fixes, and enhancements. #### .NET 10 Features - \[net10.0] Merge main to net10.0 by [@​PureWeen](https://github.com/PureWeen) in dotnet/maui#33323 #### CollectionView - Fixed the NRE in CarouselViewController on iOS 15.5 & 16.4 by [@​Ahamed-Ali](https://github.com/Ahamed-Ali) in dotnet/maui#30838 <details> <summary>🔧 Fixes</summary> - [NRE in CarouselViewController on iOS 15.5 & 16.4](dotnet/maui#28557) </details> - \[iOS, macOS] Fixed CollectionView group header size changes with ItemSizingStrategy by [@​NanthiniMahalingam](https://github.com/NanthiniMahalingam) in dotnet/maui#33161 <details> <summary>🔧 Fixes</summary> - [\[NET 10\] I6\_Grouping - Grouping_with_variable_sized_items changing the 'ItemSizingStrategy' also changes the header size.](dotnet/maui#33130) </details> - \[iOS]\[CV2] Fix page can be dragged down, and it would cause an extra space between Header and EmptyView text by [@​devanathan-vaithiyanathan](https://github.com/devanathan-vaithiyanathan) in dotnet/maui#31840 <details> <summary>🔧 Fixes</summary> - [I8\_Header_and_Footer_Null - The page can be dragged down, and it would cause an extra space between Header and EmptyView text.](dotnet/maui#31465) </details> - \[iOS] Fixed the Items not displayed properly in CarouselView2 by [@​Ahamed-Ali](https://github.com/Ahamed-Ali) in dotnet/maui#31336 <details> <summary>🔧 Fixes</summary> - [\[iOS\] Items are not updated properly in CarouselView2.](dotnet/maui#31148) </details> #### Docs - Add comprehensive README to Microsoft.Maui.Controls NuGet package by [@​jfversluis](https://github.com/jfversluis) via [@​Copilot](https://github.com/Copilot) in dotnet/maui#32835 <details> <summary>🔧 Fixes</summary> - [Improve the MAUI Nuget readme.md file](dotnet/maui#31969) </details> - Improve Controls Core API docs 2 by [@​jfversluis](https://github.com/jfversluis) in https://github.com/dotnet...
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