Fix CollectionView Header is not visible when ItemsSource is not set and an EmptyView is set in iOS, Mac platform#34989
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34989Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34989" |
|
Hey there @@KarthikRajaKalaimani! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Fixes an iOS/MacCatalyst CollectionView rendering issue where global header/footer aren’t shown when ItemsSource is null/empty and an EmptyView is set (due to UICollectionViewCompositionalLayout not rendering boundary supplementary items when there are 0 sections).
Changes:
- Override
StructuredItemsViewController2.NumberOfSectionsto return a minimum of 1 section when header/footer exists and the source would otherwise report 0 sections (non-grouped only). - Add a HostApp repro page (
Issue34897) with header/footer/empty view and AutomationIds. - Add an Appium UI test (
Issue34897) to validate header/footer/empty view elements are present.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Controls/src/Core/Handlers/Items2/iOS/StructuredItemsViewController2.cs | Ensures at least one section is reported so compositional layout renders global header/footer for empty sources (non-grouped). |
| src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt | Records new public override API surface for iOS. |
| src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt | Records new public override API surface for MacCatalyst. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue34897.cs | Adds a repro page with AutomationIds for header/footer/empty view. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34897.cs | Adds an Appium test that waits for header/footer/empty view elements. |
| @@ -0,0 +1,23 @@ | |||
| #if TEST_FAILS_ON_WINDOWS // This test fails on Windows because we no longer works with CollectionView on Windows. | |||
There was a problem hiding this comment.
The file-level comment is grammatically incorrect and misleading (CollectionView still exists on Windows). Please update it to accurately describe why this test is excluded on Windows (e.g., EmptyView/header/footer elements not being accessible via Automation on Windows) and ideally reference the tracking issue if there is one.
| #if TEST_FAILS_ON_WINDOWS // This test fails on Windows because we no longer works with CollectionView on Windows. | |
| #if TEST_FAILS_ON_WINDOWS // Excluded on Windows because the CollectionView EmptyView, header, and footer are not reliably accessible via UI Automation there. |
| @@ -0,0 +1,54 @@ | |||
| using Microsoft.Maui.Controls; | |||
| using System.Collections.ObjectModel; | |||
There was a problem hiding this comment.
System.Collections.ObjectModel is not used in this file. Please remove the unused using to avoid IDE0005/style-analyzer noise in builds.
| using System.Collections.ObjectModel; |
🚦 Gate — Test Before and After Fix
🚦 Gate Session —
|
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue34897 Issue34897 |
✅ FAIL — 283s | ✅ PASS — 102s |
🔴 Without fix — 🖥️ Issue34897: FAIL ✅ · 283s
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 647 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 669 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 7.85 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 18.98 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 18.99 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 19.01 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 19.02 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 19.03 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 19.03 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/maps/src/Maps.csproj (in 19.06 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 19.05 sec).
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
Detected signing identity:
Code Signing Key: "" (-)
Provisioning Profile: "" () - no entitlements
Bundle Id: com.microsoft.maui.uitests
App Id: com.microsoft.maui.uitests
Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
Optimizing assemblies for size. This process might take a while.
Build succeeded.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
1 Warning(s)
0 Error(s)
Time Elapsed 00:02:12.62
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 734 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 747 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 734 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 739 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 751 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 0.7 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 771 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 824 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 939 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 1.9 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 2.91 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 3.32 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj (in 3.34 sec).
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.06] Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.19] Discovered: Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 4/16/2026 5:06:13 AM FixtureSetup for Issue34897(iOS)
>>>>> 4/16/2026 5:06:17 AM CollectionViewHeaderVisibleWithEmptyViewAndNullItemsSource Start
>>>>> 4/16/2026 5:06:33 AM CollectionViewHeaderVisibleWithEmptyViewAndNullItemsSource Stop
>>>>> 4/16/2026 5:06:33 AM Log types: syslog, crashlog, performance, safariConsole, safariNetwork, server
Failed CollectionViewHeaderVisibleWithEmptyViewAndNullItemsSource [15 s]
Error Message:
System.TimeoutException : Timed out waiting for element...
Stack Trace:
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.Issues.Issue34897.CollectionViewHeaderVisibleWithEmptyViewAndNullItemsSource() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34897.cs:line 19
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
NUnit Adapter 4.5.0.0: Test execution complete
Test Run Failed.
Total tests: 1
Failed: 1
Total time: 1.3541 Minutes
🟢 With fix — 🖥️ Issue34897: PASS ✅ · 102s
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 377 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 387 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 390 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 435 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 450 ms).
6 of 11 projects are up-to-date for restore.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
Detected signing identity:
Code Signing Key: "" (-)
Provisioning Profile: "" () - no entitlements
Bundle Id: com.microsoft.maui.uitests
App Id: com.microsoft.maui.uitests
Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
Optimizing assemblies for size. This process might take a while.
Build succeeded.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
1 Warning(s)
0 Error(s)
Time Elapsed 00:00:55.77
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 380 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 397 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 355 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 412 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 434 ms).
8 of 13 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13856150
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.05] Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.15] Discovered: Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 4/16/2026 5:08:13 AM FixtureSetup for Issue34897(iOS)
>>>>> 4/16/2026 5:08:17 AM CollectionViewHeaderVisibleWithEmptyViewAndNullItemsSource Start
>>>>> 4/16/2026 5:08:17 AM CollectionViewHeaderVisibleWithEmptyViewAndNullItemsSource Stop
Passed CollectionViewHeaderVisibleWithEmptyViewAndNullItemsSource [219 ms]
NUnit Adapter 4.5.0.0: Test execution complete
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 17.6740 Seconds
📁 Fix files reverted (3 files)
src/Controls/src/Core/Handlers/Items2/iOS/StructuredItemsViewController2.cssrc/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
🤖 AI Summary
📊 Review Session —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34989 | Override NumberOfSections to return 1 when count==0 and header/footer configured (non-grouped only) |
✅ PASSED (Gate) | StructuredItemsViewController2.cs, PublicAPI ×2 |
Original PR |
🔬 Code Review — Deep Analysis
Code Review — PR #34989
Independent Assessment
What this changes: Overrides NumberOfSections in StructuredItemsViewController2<TItemsView> to return at least 1 when the data source would return 0 but a Header, Footer, HeaderTemplate, or FooterTemplate is configured. A guard prevents this from applying to grouped collections. The PublicAPI.Unshipped.txt files for iOS and macCatalyst are updated, and a new UI test reproduces the reported scenario (null ItemsSource + Header + Footer + EmptyView).
Inferred motivation: UICollectionViewCompositionalLayout silently skips rendering global boundary supplementary items (configured on NSCollectionLayoutConfiguration) when the data source reports 0 sections. This is an iOS layout engine quirk with no workaround other than reporting a phantom non-zero section count.
Reconciliation with PR Narrative
Author claims: iOS/macCatalyst UICollectionViewCompositionalLayout doesn't render Header/Footer when ItemsSource is null/empty because 0 sections suppresses global boundary items. The fix returns 1 phantom section to satisfy the layout engine; a guard prevents crashes on grouped collections.
Agreement/disagreement: Code exactly matches the stated approach. Root cause analysis is accurate and matches the iOS layout engine's documented behavior.
Findings
💡 Suggestion — Test coverage is thin for adjacent CollectionView scenarios
Per §21 of the review rules, CollectionView Header/Footer changes "must be tested across all four: empty collection, single item, many items, and with grouping." The new test covers only one of these: null ItemsSource with Header + Footer + EmptyView. Two important regressions are unguarded by any new test:
- Grouped collection with Header + Footer + empty source: The code correctly guards with
!(ItemsView is GroupableItemsView { IsGrouped: true }), but there's no test to verify the guard holds — a future refactor could inadvertently remove it. - Non-null but empty
ItemsSource(new ObservableCollection<T>()): In this caseObservableItemsSource.GroupCount == 1, socount == 1from the base and the override is never reached. Header/Footer would render without the fix. A test confirming this path isn't regressed would be valuable.
⚠️ Warning — iOS UITests failing broadly in CI
maui-pr-uitests shows failures across every iOS UI test category (Border, Button, Entry, Label, etc.) — approximately 17 categories all failing. This is clearly a pre-existing infrastructure issue unrelated to this PR (the main maui-pr build, all device tests, and integration tests all pass). However, there is no CollectionView-specific UI test run visible in CI results, so it's not possible to confirm from CI output alone that the new Issue34897 test passed on the PR itself.
Devil's Advocate
Challenge on the phantom section approach: Could returning section count=1 cause GetItemsCount(collectionView, 0) to be called on the EmptySource? Yes, it will — but EmptySource.ItemCountInGroup(0) == 0, so the phantom section correctly has zero items. CheckForEmptySource() is called inside GetItemsCount, sets _isEmpty = true, and shows the EmptyView. The empty view and the header/footer coexist correctly.
Challenge on the grouped guard: !(ItemsView is GroupableItemsView { IsGrouped: true }) — if ItemsView is null, this evaluates to true, which could in theory bypass the guard. However, if ItemsView is null, all the ItemsView?.Header is not null checks will also be null-propagated to false, so the combined && condition fails and we return the unmodified count. Safe.
Challenge on null ItemsSource: The condition ItemsSource is not null filters out the transient null state that exists briefly before ViewDidLoad sets ItemsSource = CreateItemsViewSource(). At that point the base already returns 0 anyway, so this guard is correct belt-and-suspenders rather than redundant.
Am I sure about the scoping? The fix only fires when EmptySource is active (GroupCount == 0). A non-null but empty IEnumerable produces an ObservableItemsSource with GroupCount == 1, so count == 1 from the base and this override never fires. The scope of the fix is narrower than it looks — it only helps the specific case of ItemsSource = null (or explicitly disposed/cleared). This matches the issue exactly.
Verdict: LGTM
Confidence: medium
Summary: The fix is correct, well-guarded, and the approach (returning a phantom section count) is the canonical workaround for this UICollectionViewCompositionalLayout limitation. The logic handles null ItemsSource, grouped collections, and the disposal path correctly. The test covers the reported scenario. Confidence is medium rather than high only because CollectionView is the highest-regression component in the repo, the test coverage doesn't exercise the adjacent scenarios recommended by the review rules (particularly grouped collection with header/footer), and the iOS UI test CI tier is entirely red so there's no automated confirmation from CI that the new test passed.
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | claude-opus-4.6 | Override to return new class (GroupCount=1, ItemCount=0) when header/footer configured (non-grouped) | PASS | StructuredItemsViewController2.cs, PublicAPI 2 |
Data-source-level fix; keeps ViewController/source in sync |
| 2 | claude-sonnet-4.6 | Post-process after ViewDidLoad/UpdateItemsSource; replace EmptySource with existing (GroupCount=1); no new class | PASS | StructuredItemsViewController2.cs, PublicAPI 2 |
Uses existing ListSource; data source fully consistent |
| 3 | gpt-5.3-codex | Render header/footer as standalone UIView subviews directly on UICollectionView when source has 0 groups; bypasses UIKit supplementary views PASS | StructuredItemsViewController2.cs |
Most invasive (~114 lines); no section count change | |
| 4 | gpt-5.4 | Synthetic layout attributes in FAIL | LayoutFactory2.cs |
Layout-only fix UIKit requires data source backing | insufficient LayoutFactory2 |
| PR | PR #34989 | Override to return 1 (phantom section) when count==0 and header/footer configured (non-grouped only) | PASSED (Gate) | StructuredItemsViewController2.cs, PublicAPI 2 |
Simplest/most surgical (20 lines) |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | No | Solution space covered; two viable fix classes: phantom-section (PR/1/2) or bypass UIKit supplementary views (3) |
| gpt-5.3-codex | 2 | Yes | Inject hidden sentinel item (1 zero-sized more complex variant of phantom-section class; not run |
Exhausted: Yes
Selected Fix: PR's simplest and most surgical (single override, 20 lines). Phantom-section approach confirmed canonical workaround for UICollectionViewCompositionalLayout limitation. Three independent alternatives that passed were more complex or more invasive.fix
📋 Report — Final Recommendation
✅ Final Recommendation: APPROVE
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Issue #34897 — iOS/macCatalyst CollectionView header/footer invisible when ItemsSource=null + EmptyView |
| Code Review | LGTM (medium) | 0 errors, 1 warning (pre-existing CI infra issue), 1 suggestion (thin test coverage) |
| Gate | ✅ PASSED | ios — test fails without fix, passes with fix |
| Try-Fix | ✅ COMPLETE | 4 attempts; 3 passing (1–3), 1 failing (4); PR's fix selected as simplest |
| Report | ✅ COMPLETE |
Code Review Impact on Try-Fix
No ❌ Errors were found in code review, so try-fix models were not asked to address any mandatory code changes. The warning about thin test coverage and the grouped-collection failure-mode probe guided models to focus on the grouping guard. Attempt 4 (layout-only approach) empirically confirmed the failure mode identified by the devil's advocate analysis: UIKit requires data source backing, not just layout attributes.
Summary
PR #34989 fixes a confirmed iOS/macCatalyst bug where CollectionView global boundary supplementary items (Header/Footer) fail to render when ItemsSource is null and an EmptyView is configured. The fix is correct, well-guarded, and the simplest viable approach. The gate confirmed the bug and fix. Three independent alternative approaches were explored and all passed, validating the fix class. The PR's own approach was the simplest of all passing candidates.
Two minor pre-existing code review findings (unused using System.Collections.ObjectModel; and a misleading #if TEST_FAILS_ON_WINDOWS comment) were flagged by the prior Copilot code reviewer and are unresolved. These are low-severity and do not affect correctness.
Root Cause
UICollectionViewCompositionalLayout does not render global boundary supplementary items (configured via NSCollectionLayoutConfiguration) when the data source reports 0 sections. When ItemsSource is null on the CollectionView, ItemsSourceFactory.Create(null) returns an EmptySource with GroupCount == 0, causing the base NumberOfSections to return 0, which silently suppresses header/footer rendering. Android is unaffected because its layout engine handles the empty case differently.
Fix Quality
The fix is minimal (20 lines), correctly scoped, and well-commented. Key correctness properties:
count == 0 && ItemsSource is not null: filters out the transient pre-ViewDidLoadnull state!(ItemsView is GroupableItemsView { IsGrouped: true }): prevents crash on grouped collections whose per-section supplementary items would reference non-existent group dataItemsView?.Header/Footer != nullcondition: only activates when header/footer actually configured- Phantom section returns 0 items (
EmptySource.ItemCountInGroup(0) == 0), soEmptyViewcontinues to display correctly PublicAPI.Unshipped.txtentries correctly added for iOS and macCatalyst
The PR's NumberOfSections override is the simplest and most surgical of the tested approaches. Alternatives that passed (CreateItemsViewSource override, post-ViewDidLoad source swap) were functionally correct but more complex.
Selected Fix: PR — Gate passed, code review LGTM, PR's fix is simplest among passing alternatives.
…and an EmptyView is set in iOS, Mac platform (dotnet#34989) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: On iOS and macCatalyst, the CollectionView Header/Footer is not visible when ItemsSource is not set (or empty) and an EmptyView is configured. ### Root Cause: iOS/macCatalyst uses UICollectionViewCompositionalLayout, which renders the Header and Footer as global boundary supplementary items configured on NSCollectionLayoutConfiguration. The core problem is that UICollectionViewCompositionalLayout simply does not render these global boundary items when the collection reports 0 sections. When ItemsSource is null or empty, the data source returns 0 sections, causing the layout engine to skip rendering the Header/Footer entirely. Android is unaffected because its layout engine handles this case differently. ### Description of Change: The fix overrides NumberOfSections in StructuredItemsViewController2.cs to return at least 1 section whenever the data source would return 0 sections but a Header or Footer is configured. This tricks the UICollectionViewCompositionalLayout into having one phantom empty section, which satisfies the layout engine and causes the Header/Footer to render correctly. A guard ensures this only applies to non-grouped collections — grouped collections use per-section supplementary items and would crash if given a phantom section that has no corresponding group data in the source. **Tested the behavior in the following platforms:** - [ ] Android - [ ] Windows - [x] iOS - [x] Mac ### Reference: N/A ### Issues Fixed: Fixes dotnet#34897 ### Screenshots | Before | After | |---------|--------| | <img width="300" height="600" alt="Before_34897" src="https://github.com/user-attachments/assets/9e883564-d5cd-4f89-b238-f5b7c8bf434c" /> | <img width="300" height="600" alt="After_34897" src="https://github.com/user-attachments/assets/eb045764-1bee-4f52-b97f-65e39f0cdfc7" /> |
…and an EmptyView is set in iOS, Mac platform (#34989) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: On iOS and macCatalyst, the CollectionView Header/Footer is not visible when ItemsSource is not set (or empty) and an EmptyView is configured. ### Root Cause: iOS/macCatalyst uses UICollectionViewCompositionalLayout, which renders the Header and Footer as global boundary supplementary items configured on NSCollectionLayoutConfiguration. The core problem is that UICollectionViewCompositionalLayout simply does not render these global boundary items when the collection reports 0 sections. When ItemsSource is null or empty, the data source returns 0 sections, causing the layout engine to skip rendering the Header/Footer entirely. Android is unaffected because its layout engine handles this case differently. ### Description of Change: The fix overrides NumberOfSections in StructuredItemsViewController2.cs to return at least 1 section whenever the data source would return 0 sections but a Header or Footer is configured. This tricks the UICollectionViewCompositionalLayout into having one phantom empty section, which satisfies the layout engine and causes the Header/Footer to render correctly. A guard ensures this only applies to non-grouped collections — grouped collections use per-section supplementary items and would crash if given a phantom section that has no corresponding group data in the source. **Tested the behavior in the following platforms:** - [ ] Android - [ ] Windows - [x] iOS - [x] Mac ### Reference: N/A ### Issues Fixed: Fixes #34897 ### Screenshots | Before | After | |---------|--------| | <img width="300" height="600" alt="Before_34897" src="https://github.com/user-attachments/assets/9e883564-d5cd-4f89-b238-f5b7c8bf434c" /> | <img width="300" height="600" alt="After_34897" src="https://github.com/user-attachments/assets/eb045764-1bee-4f52-b97f-65e39f0cdfc7" /> |
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details:
On iOS and macCatalyst, the CollectionView Header/Footer is not visible when ItemsSource is not set (or empty) and an EmptyView is configured.
Root Cause:
iOS/macCatalyst uses UICollectionViewCompositionalLayout, which renders the Header and Footer as global boundary supplementary items configured on NSCollectionLayoutConfiguration. The core problem is that UICollectionViewCompositionalLayout simply does not render these global boundary items when the collection reports 0 sections. When ItemsSource is null or empty, the data source returns 0 sections, causing the layout engine to skip rendering the Header/Footer entirely. Android is unaffected because its layout engine handles this case differently.
Description of Change:
The fix overrides NumberOfSections in StructuredItemsViewController2.cs to return at least 1 section whenever the data source would return 0 sections but a Header or Footer is configured. This tricks the UICollectionViewCompositionalLayout into having one phantom empty section, which satisfies the layout engine and causes the Header/Footer to render correctly. A guard ensures this only applies to non-grouped collections — grouped collections use per-section supplementary items and would crash if given a phantom section that has no corresponding group data in the source.
Tested the behavior in the following platforms:
Reference:
N/A
Issues Fixed:
Fixes #34897
Screenshots