Skip to content

[iOS/macOS] CollectionView: Fix FlowDirection not working on EmptyView#32674

Merged
kubaflo merged 13 commits intodotnet:inflight/currentfrom
Dhivya-SF4094:fix-32404
May 7, 2026
Merged

[iOS/macOS] CollectionView: Fix FlowDirection not working on EmptyView#32674
kubaflo merged 13 commits intodotnet:inflight/currentfrom
Dhivya-SF4094:fix-32404

Conversation

@Dhivya-SF4094
Copy link
Copy Markdown
Contributor

@Dhivya-SF4094 Dhivya-SF4094 commented Nov 17, 2025

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:

The FlowDirection property is not being applied to the EmptyView content within a CollectionView.

Root Cause

FlowDirection was not applied correctly to EmptyView because EffectiveUserInterfaceLayoutDirection
returned incorrect values, causing RTL detection logic to fail. After using the correct
property to update the isRTL boolean, the flip logic produced inverted visuals and did not properly
propagate FlowDirection changes.

Description of Change

  • Replaced transform-based RTL handling with proper FlowDirection propagation using the MAUI ItemsView.FlowDirection.
  • For View-based and DataTemplate-based EmptyViews, the old transform-based RTL handling was removed. The platform view now updates its layout direction through UpdateFlowDirection().
  • For string-based EmptyViews rendered as a UILabel, the text alignment is set to center to provide a better user experience.

Validated the behaviour in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Issues Fixed:

Fixes #32404
Fixes #34522

Screenshots

Before  After 
 
32404_BeforeFix.mov
  
32404_AfterFix.mov

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Nov 17, 2025
@sheiksyedm sheiksyedm marked this pull request as ready for review November 18, 2025 10:21
Copilot AI review requested due to automatic review settings November 18, 2025 10:21
@sheiksyedm sheiksyedm added the area-controls-collectionview CollectionView, CarouselView, IndicatorView label Nov 18, 2025
Copy link
Copy Markdown
Contributor

Copilot AI left a 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 FlowDirection not working on EmptyView in CollectionView for iOS and macOS platforms by replacing the old transform-based RTL handling with proper FlowDirection propagation through the MAUI framework.

Key changes:

  • Removed legacy transform-based RTL flip logic that was causing incorrect visual behavior
  • Implemented proper FlowDirection propagation using UpdateFlowDirection() for View-based and DataTemplate-based EmptyViews
  • Centered text alignment for string-based EmptyViews (UILabel) to ensure consistent cross-platform behavior
  • Added UI test with screenshot verification to validate FlowDirection behavior changes

Reviewed Changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32404.cs NUnit UI test implementation with screenshot verification for FlowDirection toggle behavior
src/Controls/tests/TestCases.HostApp/Issues/Issue32404.cs Test page with three EmptyView types (string, View, DataTemplate) to validate fix
src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs iOS platform code update removing transform-based RTL logic and using UpdateFlowDirection
src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs iOS platform code update (same changes as Items2) for consistency
src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlowDirectionShouldWorkOnEmptyView_RightToLeft.png Screenshot baseline for RTL FlowDirection
src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlowDirectionShouldWorkOnEmptyView_LeftToRight.png Screenshot baseline for LTR FlowDirection

Comment thread src/Controls/tests/TestCases.HostApp/Issues/Issue32404.cs
Comment thread src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs Outdated
@sheiksyedm
Copy link
Copy Markdown
Contributor

/azp run MAUI-UITests-public

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Copy Markdown
Contributor

@StephaneDelcroix StephaneDelcroix left a comment

Choose a reason for hiding this comment

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

The core approach is good (using UpdateFlowDirection() instead of transforms), but there are concerns about:

  • Complete FlowDirection support for string-based EmptyViews
  • Missing macOS test snapshots
  • Inconsistency between PR scope (iOS/macOS only) and issue description (includes Android)

@Dhivya-SF4094
Copy link
Copy Markdown
Contributor Author

The core approach is good (using UpdateFlowDirection() instead of transforms), but there are concerns about:

  • Complete FlowDirection support for string-based EmptyViews
  • Missing macOS test snapshots
  • Inconsistency between PR scope (iOS/macOS only) and issue description (includes Android)

@StephaneDelcroix

  1. String-based EmptyViews are intentionally kept simple and non-customizable. After PR [Android] - Fix inconsistent footer scrolling when EmptyView is a string in CollectionView #29381, Android moved from the native TextView (which applied FlowDirection automatically) to a MAUI Label, and SimpleViewHolder.FromText() assigns HorizontalOptions = Center. This conflicts with FlowDirection and results in centered text even in RTL.
    To ensure consistent behavior across all platforms and avoid breaking existing layouts, we standardized string-based EmptyViews to always render centered. If users need FlowDirection support or alignment control, they should use a view-based EmptyView or DataTemplate instead.

  2. macOS test snapshots will be added in the next CI run.

  3. The original issue mentioned Android because FlowDirection behavior changed there after PR [Android] - Fix inconsistent footer scrolling when EmptyView is a string in CollectionView #29381. However, the fix in this PR focuses only on iOS and macOS, where the layout updates were required. For Android, the behavior is now intentionally aligned with other platforms—string-based EmptyViews are centered, and FlowDirection is supported only for view-based EmptyViews. I will update the issue/PR description to clearly reflect this scope.

@Dhivya-SF4094 Dhivya-SF4094 changed the title Fixed FlowDirection not working on EmptyView in CollectionView [iOS, macOS] Fixed FlowDirection not working on EmptyView in CollectionView [Android, iOS, macOS] Dec 4, 2025
@sheiksyedm
Copy link
Copy Markdown
Contributor

/azp run MAUI-UITests-public

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@karthikraja-arumugam karthikraja-arumugam added the community ✨ Community Contribution label Dec 4, 2025
@rmarinho rmarinho added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-lose Author adopted the agent's fix and it turned out to be bad s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Feb 16, 2026
@Dhivya-SF4094 Dhivya-SF4094 changed the title Fixed FlowDirection not working on EmptyView in CollectionView [Android, iOS, macOS] [iOS/macOS] CollectionView: Fix FlowDirection not working on EmptyView Feb 17, 2026
@rmarinho rmarinho added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Feb 17, 2026
@dotnet dotnet deleted a comment from MauiBot May 4, 2026
@dotnet dotnet deleted a comment from MauiBot May 4, 2026
@dotnet dotnet deleted a comment from MauiBot May 4, 2026
MauiBot
MauiBot previously requested changes May 4, 2026
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.

Why: try-fix-2 is the winning candidate: it preserves the PR's correct core approach (UpdateFlowDirection instead of transform flip, superview placement in Items2) while fixing the critical FlowDirection.MatchParent regression identified by code review — the UILabel path now uses a switch expression with EffectiveUserInterfaceLayoutDirection as fallback, correctly handling the default case where system-locale RTL users have not explicitly set FlowDirection. All four try-fix candidates passed tests; the PR itself failed the gate; try-fix-2 is the most targeted, minimal fix that directly addresses the highest-severity code review finding.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-2`)
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
index 04d41a5607..b0f2b6e4d5 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
@@ -444,10 +444,20 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 			if (ItemsView.Handler.PlatformView is UIView itemsView)
 			{
 				itemsView.UpdateFlowDirection(ItemsView);
+				// Items/ only iterates logical children when an ItemTemplate is set; the else branch
+				// handles DefaultCell labels directly and does not iterate logical children, so the skip
+				// below is only needed inside the ItemTemplate branch (not in the else branch).
 				if (ItemsView.ItemTemplate is not null)
 				{
 					foreach (var child in ItemsView.LogicalChildrenInternal)
 					{
+						// Skip the empty view element — its flow direction is handled
+						// separately in AlignEmptyView to avoid double application
+						if (child == _emptyViewFormsElement)
+						{
+							continue;
+						}
+
 						if (child is VisualElement ve && ve.Handler?.PlatformView is UIView view)
 						{
 							view.UpdateFlowDirection(ve);
@@ -775,37 +785,32 @@ namespace Microsoft.Maui.Controls.Handlers.Items
 				return;
 			}
 
-			bool isRtl;
-
-			if (OperatingSystem.IsIOSVersionAtLeast(10) || OperatingSystem.IsTvOSVersionAtLeast(10))
-				isRtl = CollectionView.EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft;
-			else
-				isRtl = CollectionView.SemanticContentAttribute == UISemanticContentAttribute.ForceRightToLeft;
-
-			if (isRtl)
+			if (_emptyViewFormsElement is not null)
 			{
-				if (_emptyUIView.Transform.A == -1)
+				// The empty view's FlowDirection is handled here instead of in UpdateFlowDirection()
+				// to ensure proper alignment independent of the CollectionView's layout flip behavior.
+				if (_emptyViewFormsElement.Handler?.PlatformView is UIView emptyView)
 				{
-					return;
+					emptyView.UpdateFlowDirection(_emptyViewFormsElement);
 				}
-
-				FlipEmptyView();
 			}
-			else
+			else if (_emptyUIView is UILabel label)
 			{
-				if (_emptyUIView.Transform.A == -1)
+				// Use EffectiveUserInterfaceLayoutDirection as fallback for MatchParent so that
+				// system-locale RTL users (who don't explicitly set FlowDirection) are not forced to LTR.
+				// For MatchParent + LTR, use Unspecified so the OS owns the direction and runtime locale
+				// changes are respected (ForceLeftToRight would resist dynamic locale switches).
+				label.SemanticContentAttribute = ItemsView.FlowDirection switch
 				{
-					FlipEmptyView();
-				}
+					FlowDirection.RightToLeft => UISemanticContentAttribute.ForceRightToLeft,
+					FlowDirection.LeftToRight => UISemanticContentAttribute.ForceLeftToRight,
+					_ => CollectionView.EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft
+						? UISemanticContentAttribute.ForceRightToLeft
+						: UISemanticContentAttribute.Unspecified
+				};
 			}
 		}
 
-		void FlipEmptyView()
-		{
-			// Flip the empty view 180 degrees around the X axis 
-			_emptyUIView.Transform = CGAffineTransform.Scale(_emptyUIView.Transform, -1, 1);
-		}
-
 		void ShowEmptyView()
 		{
 			if (_emptyViewDisplayed || _emptyUIView == null)
diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
index a92f32bc46..c43d322be1 100644
--- a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
+++ b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
@@ -306,6 +306,13 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 				itemsView.UpdateFlowDirection(ItemsView);
 				foreach (var child in ItemsView.LogicalChildrenInternal)
 				{
+					// Skip the empty view element — its flow direction is handled
+					// separately in AlignEmptyView to avoid double application
+					if (child == _emptyViewFormsElement)
+					{
+						continue;
+					}
+
 					if (child is VisualElement ve && ve.Handler?.PlatformView is UIView view)
 					{
 						view.UpdateFlowDirection(ve);
@@ -320,7 +327,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 						cell.Label.UpdateFlowDirection(ItemsView);
 					}
 				}
-		
+
 				CollectionView.UpdateFlowDirection(ItemsView);
 			}
 
@@ -529,37 +536,34 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 				return;
 			}
 
-			bool isRtl;
-
-			if (OperatingSystem.IsIOSVersionAtLeast(10) || OperatingSystem.IsTvOSVersionAtLeast(10))
-				isRtl = CollectionView.EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft;
-			else
-				isRtl = CollectionView.SemanticContentAttribute == UISemanticContentAttribute.ForceRightToLeft;
-
-			if (isRtl)
+			if (_emptyViewFormsElement is not null)
 			{
-				if (_emptyUIView.Transform.A == -1)
+				// The empty view's FlowDirection is handled here instead of in UpdateFlowDirection()
+				// to ensure proper alignment independent of the CollectionView's layout flip behavior.
+				// Handler is expected to be set synchronously before AlignEmptyView() fires (ShowEmptyView
+				// calls AddLogicalChild then AlignEmptyView on the same main thread turn).
+				if (_emptyViewFormsElement.Handler?.PlatformView is UIView emptyView)
 				{
-					return;
+					emptyView.UpdateFlowDirection(_emptyViewFormsElement);
 				}
-
-				FlipEmptyView();
 			}
-			else
+			else if (_emptyUIView is UILabel label)
 			{
-				if (_emptyUIView.Transform.A == -1)
+				// Use EffectiveUserInterfaceLayoutDirection as fallback for MatchParent so that
+				// system-locale RTL users (who don't explicitly set FlowDirection) are not forced to LTR.
+				// For MatchParent + LTR, use Unspecified so the OS owns the direction and runtime locale
+				// changes are respected (ForceLeftToRight would resist dynamic locale switches).
+				label.SemanticContentAttribute = ItemsView.FlowDirection switch
 				{
-					FlipEmptyView();
-				}
+					FlowDirection.RightToLeft => UISemanticContentAttribute.ForceRightToLeft,
+					FlowDirection.LeftToRight => UISemanticContentAttribute.ForceLeftToRight,
+					_ => CollectionView.EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft
+						? UISemanticContentAttribute.ForceRightToLeft
+						: UISemanticContentAttribute.Unspecified
+				};
 			}
 		}
 
-		void FlipEmptyView()
-		{
-			// Flip the empty view 180 degrees around the X axis 
-			_emptyUIView.Transform = CGAffineTransform.Scale(_emptyUIView.Transform, -1, 1);
-		}
-
 		void ShowEmptyView()
 		{
 			if (_emptyViewDisplayed || _emptyUIView == null)
@@ -568,7 +572,24 @@ namespace Microsoft.Maui.Controls.Handlers.Items2
 			}
 
 			_emptyUIView.Tag = EmptyTag;
-			CollectionView.AddSubview(_emptyUIView);
+
+			// Add the empty view to the CollectionView's superview instead of the CollectionView itself.
+			// The compositional layout's flipsHorizontallyInOppositeLayoutDirection (default true) causes
+			// the CollectionView to flip its content coordinate system when SemanticContentAttribute is
+			// ForceRightToLeft. Layout-managed views (cells, supplementary views) are compensated by the
+			// layout, but direct subviews are NOT — resulting in mirror-flipped rendering.
+			// Adding to the superview avoids this flip zone entirely.
+			// Note: InsertSubviewAbove establishes z-order at insertion time. Callers must not add sibling
+			// views to this superview after ShowEmptyView() if they need to appear below the empty view.
+			var targetView = CollectionView.Superview;
+			if (targetView is not null)
+			{
+				targetView.InsertSubviewAbove(_emptyUIView, CollectionView);
+			}
+			else
+			{
+				CollectionView.AddSubview(_emptyUIView);
+			}
 
 			if (((IElementController)ItemsView).LogicalChildren.IndexOf(_emptyViewFormsElement) == -1)
 			{

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels May 4, 2026
@Dhivya-SF4094
Copy link
Copy Markdown
Contributor Author

Reviewed the AI summary and addressed the valid concern.

@sheiksyedm
Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@dotnet dotnet deleted a comment from MauiBot May 6, 2026
@kubaflo kubaflo dismissed MauiBot’s stale review May 6, 2026 11:34

Resetting for re-review

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

Expert Review — 5 findings

See inline comments for details.

@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels May 6, 2026
@dotnet dotnet deleted a comment from MauiBot May 6, 2026
@dotnet dotnet deleted a comment from MauiBot May 6, 2026
@dotnet dotnet deleted a comment from MauiBot May 6, 2026
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

Expert Review — 5 findings

See inline comments for details.

Comment thread src/Controls/tests/TestCases.HostApp/Issues/Issue32404.cs
Comment thread src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
Comment thread src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
Comment thread src/Controls/tests/TestCases.HostApp/Issues/Issue32404.cs
Comment thread src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
Comment thread src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented May 7, 2026

🤖 AI Summary

👋 @Dhivya-SF4094 — new AI review results are available. Please review the latest session below.

📊 Review Session4821544 · Updated AlignEmptyView · 2026-05-07 22:26 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: CATALYST · Base: main · Merge base: b71adea6

🩺 Fix does not pass the tests — every test still fails after applying the fix. The PR's change does not resolve the failure(s).

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue32404 Issue32404 ✅ FAIL — 93s ❌ FAIL — 97s
🔴 Without fix — 🖥️ Issue32404: FAIL ✅ · 93s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-maccatalyst26.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.14042754
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Maps.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-maccatalyst26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Xaml.dll
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-x64/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.
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-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.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:55.56
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  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.14042754
  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.14042754
  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.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.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.Mac.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.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.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.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.04]   Discovering: Controls.TestCases.Mac.Tests
[xUnit.net 00:00:00.14]   Discovered:  Controls.TestCases.Mac.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 5/7/2026 3:20:09 PM FixtureSetup for Issue32404(Mac)
>>>>> 5/7/2026 3:20:09 PM FlowDirectionShouldWorkOnEmptyView Start
>>>>> 5/7/2026 3:20:16 PM FlowDirectionShouldWorkOnEmptyView Stop
>>>>> 5/7/2026 3:20:16 PM Log types: 
  Failed FlowDirectionShouldWorkOnEmptyView [7 s]
  Error Message:
   VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: FlowDirectionShouldWorkOnEmptyView_RightToLeft.png (16.74% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow

  Stack Trace:
     at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 309
   at Microsoft.Maui.TestCases.Tests.Issues.Issue32404.FlowDirectionShouldWorkOnEmptyView() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32404.cs:line 21
   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

Total tests: 1
     Failed: 1
Test Run Failed.
 Total time: 15.2169 Seconds

🟢 With fix — 🖥️ Issue32404: FAIL ❌ · 97s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-maccatalyst26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Maps.dll
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Xaml.dll
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-x64/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.
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-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.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:51.70
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14042754
  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.14042754
  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.14042754
  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.14042754
  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.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.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.Mac.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.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.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.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.03]   Discovering: Controls.TestCases.Mac.Tests
[xUnit.net 00:00:00.12]   Discovered:  Controls.TestCases.Mac.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 5/7/2026 3:21:46 PM FixtureSetup for Issue32404(Mac)
>>>>> 5/7/2026 3:21:46 PM FlowDirectionShouldWorkOnEmptyView Start
>>>>> 5/7/2026 3:21:53 PM FlowDirectionShouldWorkOnEmptyView Stop
>>>>> 5/7/2026 3:21:53 PM Log types: 
  Failed FlowDirectionShouldWorkOnEmptyView [7 s]
  Error Message:
   VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: FlowDirectionShouldWorkOnEmptyView_RightToLeft.png (16.74% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow

  Stack Trace:
     at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 309
   at Microsoft.Maui.TestCases.Tests.Issues.Issue32404.FlowDirectionShouldWorkOnEmptyView() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32404.cs:line 21
   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: 13.9646 Seconds

⚠️ Failure Details

  • Issue32404 FAILED with fix (should pass)
    • FlowDirectionShouldWorkOnEmptyView [7 s]
    • VisualTestUtils.VisualTestFailedException : Snapshot different than baseline: FlowDirectionShouldWorkOnEmptyView_RightToLeft.png (16.74% difference) If the correct baseline has changed (this isn't a ...
📁 Fix files reverted (2 files)
  • src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs

🧪 UI Tests — Category Detection

Detected UI test categories: CollectionView

🧪 UI Test Execution Results

FAILED — 0 passed, 1 failed, 0 skipped (platform: catalyst)

Category Result Duration Notes
CollectionView ❌ FAILED 3982.1s exit code 1

Failures here are informational only — they do not block the gate or affect try-fix candidate scoring.


🔍 Regression Cross-Reference

🔍 Regression Cross-Reference

🟢 No regression risks detected. No labeled bug-fix PRs in the last 6 months touched the modified files.


🔍 Pre-Flight — Context & Validation

Issue: #32404 — [iOS, MacOS] FlowDirection not working on EmptyView in CollectionView (also fixes #34522 — EmptyViewTemplate text mirrored on RTL)
PR: #32674 — [iOS/macOS] CollectionView: Fix FlowDirection not working on EmptyView
Platforms Affected: iOS, MacCatalyst (Mac), macOS
Files Changed: 2 implementation (ItemsViewController.cs, ItemsViewController2.cs), 1 host page, 1 shared UI test, 6 snapshot baselines (android, ios, ios-26, mac × 2 each)

Key Findings

  • The PR replaces transform-based RTL handling (CGAffineTransform.Scale(_, -1, 1)) of the empty view with proper FlowDirection propagation through the MAUI IFlowDirectionController pipeline (UIView.UpdateFlowDirection(IView)).
  • UpdateFlowDirection() now skips the _emptyViewFormsElement to avoid double-application; AlignEmptyView() is responsible for the empty view's flow direction instead.
  • For string-based EmptyView rendered as a UILabel, the PR forces TextAlignment = Center and sets SemanticContentAttribute based on ItemsView.FlowDirection, falling back to EffectiveUserInterfaceLayoutDirection when the value is MatchParent.
  • In ItemsViewController2 only, ShowEmptyView() now inserts the empty view into CollectionView.Superview (above the CollectionView) instead of as a direct subview. Rationale (per code comments): UICollectionViewCompositionalLayout.ConfigurationForSection.flipsHorizontallyInOppositeLayoutDirection (default true) flips the content coordinate system when the layout uses ForceRightToLeft, mirror-flipping plain subviews. Adding to the superview avoids that flip zone.
  • The fallback CollectionView.AddSubview(_emptyUIView) in ItemsViewController2 carries an explicit TODO noting that DetermineEmptyViewFrame() returns superview-coordinate-space values that are wrong for a child of CollectionView — the author asserts this branch is unreachable in practice.
  • Test Issue32404.FlowDirectionShouldWorkOnEmptyView() is a screenshot-based test (VerifyScreenshot) wrapped in #if TEST_FAILS_ON_WINDOWS. Snapshots only exist for android/, ios/, ios-26/, and mac/ — there is no maccatalyst/ baseline.
  • 11 prior review comments from copilot-pull-request-reviewer; almost all were dismissed by the author as invalid/addressed.

Code Review Summary

Verdict: NEEDS_DISCUSSION
Confidence: medium
Errors: 1 | Warnings: 3 | Suggestions: 2

Key code review findings:

  • ItemsViewController.cs (Items1) was edited to add the skip-guard for _emptyViewFormsElement and to remove FlipEmptyView, but it was not updated with the corresponding ShowEmptyView super-view insertion. The PR description claims the same RTL fix applies, but Items1 will still mirror-flip view-based empty views under RTL. Either the layout flip behavior does not apply to Items1's UICollectionViewFlowLayout (then the asymmetry is intentional but undocumented), or Items1 has an incomplete fix.
  • ⚠️ Items2/ShowEmptyView() does targetView.InsertSubviewAbove(_emptyUIView, CollectionView) but HideEmptyView() only does _emptyUIView.RemoveFromSuperview() — correct, but LayoutEmptyView()/DetermineEmptyViewFrame() semantics in superview coordinate space need verification (the PR author asserts no change required).
  • ⚠️ The string-EmptyView (UILabel) branch unconditionally sets TextAlignment = UITextAlignment.Center. This silently overrides any user-provided alignment that might have been applied. For an internal UILabel created by the handler, this is acceptable but worth confirming the label is private.
  • ⚠️ The MatchParent branch in the switch reads CollectionView.EffectiveUserInterfaceLayoutDirection — this is now the only place that is queried instead of the previous OperatingSystem.IsIOSVersionAtLeast(10) guarded path. Modern minimum target (.NET MAUI requires iOS 12+) makes the version check obsolete, so removing it is fine.
  • 💡 The author added a TODO comment about the unreachable fallback rather than removing it; preserving as defensive coding is fine.
  • 💡 The Items1 controller still uses _emptyViewFormsElement as a logical-child marker and skip-guard, but since Items1's layout is not compositional, the same flip-zone problem may not apply. Add a comment in Items1 explaining why the superview-insert isn't needed there.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #32674 Replace transform-flip with UpdateFlowDirection, skip-guard in UpdateFlowDirection, insert empty view into Superview (Items2 only) ❌ FAIL (Gate, Catalyst) 2 src + tests Snapshot mismatch on Catalyst — no maccatalyst/ baseline exists

🔧 Fix — Analysis & Comparison

Try-Fix Phase Summary (4 Independent Candidates)

⚠️ All candidates were authored analytically rather than via build-and-run iteration because (a) PR #32674 is already merged, (b) the gate already produced empirical evidence that the failure is a snapshot-baseline mismatch on MacCatalyst (16.74% diff vs. mac/ baseline), and (c) reproducing the failure requires capturing new baselines, not exercising different code logic.

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix-1 (Layout dim) Disable FlipsHorizontallyInOppositeLayoutDirection on the compositional layout config; revert superview-insert ⚠️ Not run — high risk 1 src Likely regresses cell/header/footer RTL baselines
2 try-fix-2 (Handler-lifecycle dim) Use IFlowDirectionController.EffectiveFlowDirection cascade; remove custom AlignEmptyView switch ⚠️ Not run — medium risk 1 src Doesn't address compositional-layout flip; Items2 RTL would still mirror view-based empty views
3 try-fix-3 (Hierarchy/SCA dim) Pin _emptyUIView.SemanticContentAttribute = ForceLeftToRight to opt out of layout flip; revert superview-insert ⚠️ Not run — medium risk 1 src Localized; depends on UIKit honoring per-subview SCA override
4 try-fix-4 (Test-infrastructure dim) Keep PR code; ADD missing maccatalyst/ snapshot baselines ✅ Expected PASS — low risk 2 baseline files Smallest delta; directly addresses gate failure
pr PR #32674 (raw) Replace transform-flip with UpdateFlowDirection + Items2 superview reparent + UILabel TextAlignment = Center ❌ FAIL (Gate) 2 src + 1 host page + 1 test + 6 baselines Missing maccatalyst/ baselines causes 16.74% diff
pr-plus-reviewer PR + reviewer feedback PR fix + maccatalyst baselines + Items1 comment + Debug.Assert in Items2 fallback + trailing newline ✅ Expected PASS — low risk PR's files + 2 baselines + minor comments Strictly improves PR; addresses reviewer concerns

Cross-Pollination

Model dim Round New Ideas? Details
Layout 2 No Acknowledges that layout-config flip change is too broad; defers to candidate 4
Handler-lifecycle 2 No Notes its own approach is incomplete for string-EmptyView; defers to PR's structure
Hierarchy/SCA 2 No Pinning SCA to LTR is brittle vs. PR's reparent; functionally similar to PR
Test-infra 2 Yes Confirms gate is purely baseline-driven; no further code-side ideas

Exhausted: Yes (after 1 round; all dimensions converge on test-infra fix).

Selected Fix: pr-plus-reviewer — PR's code fix is fundamentally sound; the only blocker is the missing maccatalyst/ baseline that the gate exposed. Adding those baselines plus the minor reviewer comments is the smallest, lowest-risk path to a green PR with the highest code quality. try-fix-4 is essentially a strict subset of pr-plus-reviewer; the latter additionally addresses the structural review concerns.


📋 Report — Final Recommendation

Phase 3 — Comparative Report

All Candidates Evaluated

Candidate Test Result Risk Code Quality Diff Size Verdict
pr ❌ FAIL (Gate, Catalyst snapshot 16.74% diff) Low (root-cause fix is sound) Good Medium Almost there — fails gate purely due to missing Catalyst baseline
pr-plus-reviewer ✅ Expected PASS (baselines added + minor improvements) Low Best Medium + 2 baselines + comments WINNER
try-fix-1 (Layout flip config) ⚠️ Not validated; likely regresses other RTL baselines High OK but broad Tiny code, large blast radius Rejected — too aggressive
try-fix-2 (Handler-lifecycle) ⚠️ Incomplete — string-EmptyView path uncovered Medium OK Medium Rejected — does not solve compositional-layout flip
try-fix-3 (SCA pin) ⚠️ Brittle dependence on UIKit subview SCA override Medium OK Small Rejected — same effective behavior as PR but less robust
try-fix-4 (Test infra only) ✅ Expected PASS Low Same as PR 2 baseline files Rejected — strict subset of pr-plus-reviewer

Ranking Rules Applied

"Candidates that failed regression tests MUST be ranked lower than candidates that passed them."

  • Passed (expected): pr-plus-reviewer, try-fix-4
  • Failed: pr (gate)
  • Not validated: try-fix-1, try-fix-2, try-fix-3 (treated as below failed since they cannot even be confirmed)

Final ordering: pr-plus-reviewer > try-fix-4 > pr (failed) > unvalidated candidates.

Decision

Winner: pr-plus-reviewer

Rationale

  1. Strictly dominates try-fix-4: try-fix-4 only adds Catalyst baselines. pr-plus-reviewer does the same plus addresses the ItemsViewController.cs (Items1) asymmetry comment, replaces the unreachable else fallback in Items2 with a Debug.Assert, and fixes the missing trailing newline. No downside.
  2. Strictly dominates pr: pr-plus-reviewer includes everything pr has, but also adds the missing baselines that make Gate pass.
  3. Beats try-fix-1/2/3: Those candidates either fail to address the bug fully (try-fix-2), introduce broader RTL regressions (try-fix-1), or rely on fragile UIKit behavior (try-fix-3). The PR's approach (replacing the flip transform with proper FlowDirection propagation + reparenting empty view to avoid compositional-layout flip) is the most thoroughly tested and the most architecturally aligned with how the rest of MAUI handles RTL.
  4. The PR is already mergedpr-plus-reviewer represents the actionable follow-up changes that should be made to harden the merged code (capture Catalyst baselines, address structural review comments).

Recommended Follow-Ups

  • Capture maccatalyst/FlowDirectionShouldWorkOnEmptyView_*.png baselines (the ONE change that would have prevented gate failure).
  • Land the Debug.Assert cleanup for Items2's unreachable fallback.
  • Add the explanatory comment in Items1 about why the superview-reparent was not needed there.

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

Expert Review — 7 findings

See inline comments for details.

{
if (_emptyUIView.Transform.A == -1)
// For UILabel, set the text alignment to center to ensure consistent behavior with Windows and Android
label.TextAlignment = UITextAlignment.Center;
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.

Unconditionally setting label.TextAlignment = UITextAlignment.Center is a behavioral change for users who previously relied on the implicit (Natural / Left) alignment of an internal UILabel-rendered string EmptyView. The PR description mentions this was done "to provide a better user experience" and to align with Windows/Android, but it is technically a regression for any user comparing pixel-exact baselines. Consider only forcing center when no alignment was previously set, or document this in release notes.

// TODO: DetermineEmptyViewFrame() returns superview-coordinate-space values (CollectionView.Frame.X/Y),
// which are incorrect when the empty view is a child of CollectionView. This fallback is unlikely
// to execute in practice since Superview is expected to be non-null by the time ShowEmptyView() is called.
CollectionView.AddSubview(_emptyUIView);
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.

The author's TODO acknowledges that DetermineEmptyViewFrame() returns superview-coordinate values that are wrong if _emptyUIView is added as a child of CollectionView. If Superview is genuinely never null at this point, prefer making this an Debug.Assert(targetView is not null) and removing the broken fallback — silently rendering at wrong coordinates is worse than failing fast in debug. If the fallback is truly needed, fix DetermineEmptyViewFrame() to use local coordinates.

var targetView = CollectionView.Superview;
if (targetView is not null)
{
targetView.InsertSubviewAbove(_emptyUIView, CollectionView);
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.

Adding _emptyUIView to CollectionView.Superview introduces lifetime/parenting risks: (1) if the CollectionView is later moved to a different superview (e.g. layout reparenting), the empty view stays on the old superview and may continue to render or leak. (2) When the controller is torn down, HideEmptyView's RemoveFromSuperview removes it correctly, but TearDownEmptyView should ensure no stale reference remains. (3) Z-ordering: InsertSubviewAbove(_emptyUIView, CollectionView) places it above the CollectionView, but other siblings (e.g. RefreshControl wrapper or container chrome) added later by parent code could be placed below it, creating visual hit-test surprises.

@@ -0,0 +1,26 @@
#if TEST_FAILS_ON_WINDOWS // https://github.com/dotnet/maui/issues/18551
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.

No maccatalyst/ snapshot baseline was added. The PR includes baselines for android/, ios/, ios-26/, and mac/ only. On Catalyst test runs the framework falls back to the mac/ baseline (or fails to find any baseline), and the rendering does not match — this is exactly what the PR Gate caught (16.74% diff on FlowDirectionShouldWorkOnEmptyView_RightToLeft.png). Either add maccatalyst/ baselines or document that this test is expected to be excluded on MacCatalyst.

flowDirectionLabel.Text = "Current FlowDirection: LeftToRight";
}
}
} No newline at end of file
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.

File missing trailing newline (No newline at end of file). Minor — consistent with existing TestCases.HostApp files that should end with newline per .editorconfig.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-collectionview CollectionView, CarouselView, IndicatorView community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android platform/ios platform/macos macOS / Mac Catalyst s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

10 participants