Skip to content

[iOS/Mac] Fix FlyoutPage RTL FlowDirection is not working#34831

Merged
kubaflo merged 6 commits into
dotnet:inflight/currentfrom
devanathan-vaithiyanathan:fix-2818
May 8, 2026
Merged

[iOS/Mac] Fix FlyoutPage RTL FlowDirection is not working#34831
kubaflo merged 6 commits into
dotnet:inflight/currentfrom
devanathan-vaithiyanathan:fix-2818

Conversation

@devanathan-vaithiyanathan
Copy link
Copy Markdown
Contributor

Issue Details

In FlyoutPage, RTL FlowDirection is not working properly at initial and runtime changes.

Description of Changes

  • Added an UpdateFlowDirection method to set the correct SemanticContentAttribute on the root view, child controller views, and navigation bar, ensuring proper mirroring for RTL layouts and correct inheritance by child handlers.
  • Updated property change handling to listen for FlowDirection changes and trigger both UpdateFlowDirection and UpdateLeftBarButton when flow direction is updated at runtime.
  • Called UpdateFlowDirection during gesture recognizer setup to ensure the correct flow direction is applied during initialization.

Issues Fixed

Fixes #34830

Tested the behavior in the following platforms.

  • Android
  • Windows
  • iOS
  • Mac
Before After
iOS
Before.mov
iOS
After.mov

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 6, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34831

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34831"

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Apr 6, 2026
@sheiksyedm sheiksyedm marked this pull request as ready for review April 14, 2026 08:23
Copilot AI review requested due to automatic review settings April 14, 2026 08:23
@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).

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 addresses RTL FlowDirection not applying correctly for FlyoutPage on iOS/macOS by explicitly updating UIKit SemanticContentAttribute (including the navigation bar) during initialization and on runtime FlowDirection changes, and adds an automated UI test coverage for the reported scenario.

Changes:

  • Update PhoneFlyoutPageRenderer (iOS) to apply and refresh SemanticContentAttribute based on MAUI FlowDirection, including runtime updates.
  • Add HostApp repro page Issue34830 for exercising RTL/LTR switching and flyout presentation.
  • Add Appium/NUnit UI tests for RTL and LTR flows using screenshot verification.

Reviewed changes

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

File Description
src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs Applies semantic direction updates on init and FlowDirection property changes (including nav bar handling) to fix RTL mirroring.
src/Controls/tests/TestCases.HostApp/Issues/Issue34830.cs Adds a targeted HostApp issue page to reproduce the RTL/LTR FlyoutPage behavior.
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34830.cs Adds screenshot-based UI tests for RTL and LTR scenarios.

Comment thread src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34830.cs Outdated
@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Apr 17, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 18, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 18, 2026
@MauiBot MauiBot added the s/agent-fix-win AI found a better alternative fix than the PR label Apr 18, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

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

Could you please add snapshots

@sheiksyedm
Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@dotnet dotnet deleted a comment from MauiBot May 3, 2026
@dotnet dotnet deleted a comment from MauiBot May 3, 2026
@dotnet dotnet deleted a comment from MauiBot May 3, 2026
MauiBot
MauiBot previously requested changes May 3, 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 #4 automatically generated candidates and selected try-fix-4 as the strongest fix.

Why: Try-fix-4 passes tests on maccatalyst, keeps screenshot-based testing (project standard), refreshes Mac snapshot baselines from actual test-environment output, and extends the renderer fix to call UpdateFlowDirection() after UpdateFlyoutPageContainers() - closing the known gap where a dynamically-replaced Detail UINavigationBar would not inherit the correct SemanticContentAttribute.

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-4`)
diff --git a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
index 9ec7e8447a..92ddee30d9 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
@@ -188,7 +188,7 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility
 			UpdateFlyoutPageContainers();
 
 			UpdateBackground();
-
+			UpdateFlowDirection();
 			UpdatePanGesture();
 			UpdateApplyShadow(((FlyoutPage)Element).OnThisPlatform().GetApplyShadow());
 			UpdatePageSpecifics();
@@ -344,7 +344,10 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility
 		void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
 		{
 			if (e.PropertyName == "Flyout" || e.PropertyName == "Detail")
+			{
 				UpdateFlyoutPageContainers();
+				UpdateFlowDirection();
+			}
 			else if (e.PropertyName == Microsoft.Maui.Controls.FlyoutPage.IsPresentedProperty.PropertyName)
 				UpdatePresented(((FlyoutPage)Element).IsPresented, true);
 			else if (e.PropertyName == Microsoft.Maui.Controls.FlyoutPage.IsGestureEnabledProperty.PropertyName)
@@ -360,6 +363,41 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility
 			else if (e.PropertyName == PlatformConfiguration.iOSSpecific.Page.PrefersHomeIndicatorAutoHiddenProperty.PropertyName ||
 					 e.PropertyName == PlatformConfiguration.iOSSpecific.Page.PrefersStatusBarHiddenProperty.PropertyName)
 				UpdatePageSpecifics();
+			else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
+			{
+				UpdateFlowDirection();
+				UpdateLeftBarButton();
+			}
+		}
+
+		void UpdateFlowDirection()
+		{
+			if (Element is null)
+				return;
+
+			var semanticAttr = IsRTL
+				? UISemanticContentAttribute.ForceRightToLeft
+				: UISemanticContentAttribute.ForceLeftToRight;
+
+			View.SemanticContentAttribute = semanticAttr;
+			_flyoutController.View.SemanticContentAttribute = semanticAttr;
+			_detailController.View.SemanticContentAttribute = semanticAttr;
+
+			if (FlyoutPage.Detail.Handler is IPlatformViewHandler detailPlatformHandler &&
+				detailPlatformHandler.ViewController is UINavigationController navController)
+			{
+				navController.View.SemanticContentAttribute = semanticAttr;
+				navController.NavigationBar.SemanticContentAttribute = semanticAttr;
+			}
+
+			if (FlyoutPage.Detail is NavigationPage detailNavPage)
+			{
+				foreach (var page in detailNavPage.Navigation.NavigationStack)
+				{
+					if (page?.Handler is IElementHandler pageHandler)
+						pageHandler.UpdateValue(nameof(IView.FlowDirection));
+				}
+			}
 		}
 
 		void LayoutChildren(bool animated)
diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithLTRDirection.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithLTRDirection.png
index f6644273e3..fc7bda92c0 100644
Binary files a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithLTRDirection.png and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithLTRDirection.png differ
diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithRTLDirection.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithRTLDirection.png
index d838a521f1..8ec55ce172 100644
Binary files a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithRTLDirection.png and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithRTLDirection.png differ

@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 3, 2026
@dotnet dotnet deleted a comment from MauiBot May 4, 2026
@kubaflo kubaflo dismissed MauiBot’s stale review May 4, 2026 08:55

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 — 4 findings

See inline comments for details.

@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented May 8, 2026

🤖 AI Summary

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

📊 Review Sessionf4499b4 · ai summary addressed · 2026-05-08 10:23 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

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

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue34830 Issue34830 ✅ FAIL — 92s ✅ PASS — 89s
🔴 Without fix — 🖥️ Issue34830: FAIL ✅ · 92s
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 341 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 314 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 350 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 382 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 401 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.14048173
  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.14048173
  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.14048173
  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.14048173
  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.14048173
  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.14048173
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14048173
  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
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14048173
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14048173
  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:00:44.43
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 361 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 366 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 359 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 345 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 402 ms).
  8 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14048173
  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.14048173
  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.14048173
  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.14048173
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.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.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.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 2 of 2 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 5/8/2026 3:10:32 AM FixtureSetup for Issue34830(iOS)
>>>>> 5/8/2026 3:10:36 AM FlyoutPageWithRTLDirection Start
>>>>> 5/8/2026 3:10:38 AM FlyoutPageWithRTLDirection Stop
>>>>> 5/8/2026 3:10:38 AM Log types: syslog, crashlog, performance, safariConsole, safariNetwork, server
  Failed FlyoutPageWithRTLDirection [2 s]
  Error Message:
   VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: FlyoutPageWithRTLDirection.png (3.00% 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) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 309
   at Microsoft.Maui.TestCases.Tests.Issues.Issue34830.FlyoutPageWithRTLDirection() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34830.cs:line 22
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

>>>>> 5/8/2026 3:10:38 AM FlyoutPageWithLTRDirection Start
>>>>> 5/8/2026 3:10:41 AM FlyoutPageWithLTRDirection Stop
>>>>> 5/8/2026 3:10:41 AM Log types: syslog, crashlog, performance, safariConsole, safariNetwork, server
  Failed FlyoutPageWithLTRDirection [3 s]
  Error Message:
   VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: FlyoutPageWithLTRDirection.png (2.93% 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) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 309
   at Microsoft.Maui.TestCases.Tests.Issues.Issue34830.FlyoutPageWithLTRDirection() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34830.cs:line 34
   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: 2
     Failed: 2
 Total time: 22.6878 Seconds

🟢 With fix — 🖥️ Issue34830: PASS ✅ · 89s
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 2.9 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 2.91 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 2.89 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 2.95 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 2.93 sec).
  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.14048173
  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.14048173
  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.14048173
  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.14048173
  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.14048173
  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.14048173
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14048173
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14048173
  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.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
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14048173
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.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
  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:41.44
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 379 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 407 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 401 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 460 ms).
  8 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.14048173
  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.14048173
  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.14048173
  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.14048173
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.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.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.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.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 2 of 2 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 5/8/2026 3:12:03 AM FixtureSetup for Issue34830(iOS)
>>>>> 5/8/2026 3:12:07 AM FlyoutPageWithRTLDirection Start
>>>>> 5/8/2026 3:12:08 AM FlyoutPageWithRTLDirection Stop
>>>>> 5/8/2026 3:12:08 AM FlyoutPageWithLTRDirection Start
  Passed FlyoutPageWithRTLDirection [1 s]
>>>>> 5/8/2026 3:12:10 AM FlyoutPageWithLTRDirection Stop
  Passed FlyoutPageWithLTRDirection [2 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 2
     Passed: 2
 Total time: 20.3276 Seconds

📁 Fix files reverted (1 files)
  • src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs

🧪 UI Tests — Category Detection

Detected UI test categories: FlyoutPage

🧪 UI Test Execution Results

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

Category Result Tests Duration Notes
FlyoutPage ❌ FAILED 65/66 (1 ❌) 699.9s exit code 1, names truncated
Show 4 of 65 passed test name(s) — stdout was truncated, full list in build log

FlyoutPage

  • Issue5412Test (2 s)
  • Issue899TestsAppCrashWhenSwitchingTabs (2 s)
  • Issue892TestsNavigateChangePagesNavigate (12 s)
  • Unreported1Test (51 ms)

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

ℹ️ This section was reconstructed from the build artifact (34831 build) after the run completed.


🔍 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

Pre-Flight — PR #34831

PR Metadata

  • Title: [iOS/Mac] Fix FlyoutPage RTL FlowDirection is not working
  • Author: devanathan-vaithiyanathan (Syncfusion partner)
  • Base / Head: mainfix-2818
  • Linked Issue: [iOS/Mac] FlyoutPage RTL FlowDirection is not working properly #34830[iOS/Mac] FlyoutPage RTL FlowDirection is not working properly
  • Labels (issue): t/bug, platform/ios, platform/macos, area-controls-flyoutpage, partner/syncfusion, s/verified, s/triaged
  • Platform under review: iOS (Mac Catalyst follows the same renderer)

Bug Summary

On iOS/Mac, when a FlyoutPage has FlowDirection = RightToLeft, the page is not mirrored
properly on initial load and runtime FlowDirection changes are also ignored. The hamburger
button stays on the left, child controllers do not flip, and the flyout panel content is
clipped/hidden.

Root Cause (per author)

PhoneFlyoutPageRenderer never propagates FlowDirection to the iOS UIView hierarchy:

  1. View.SemanticContentAttribute is not set on the renderer's root view, the flyout-
    container's view, or the detail-container's view, so child handlers that resolve
    FlowDirection.MatchParent from the parent UIView see Unspecified.
  2. UINavigationBar (used by the inner NavigationPage) does NOT inherit
    SemanticContentAttribute from its superview, so bar button items don't mirror.
  3. NavigationPage is not an IContainer/IContentView in the Core layer, so
    ViewExtensions.PropagateFlowDirection doesn't recurse into pages on the navigation
    stack — they need a manual UpdateValue(nameof(IView.FlowDirection)).
  4. HandlePropertyChanged does not listen for VisualElement.FlowDirectionProperty, so
    runtime changes are dropped.

Files Changed (classification)

File Type Notes
src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs Production (iOS/MacCatalyst) Adds UpdateFlowDirection(), hooks property change, calls in ViewDidLoad
src/Controls/tests/TestCases.HostApp/Issues/Issue34830.cs UI test host page New repro page
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34830.cs UI test (Appium snapshot) Two ordered snapshot tests (RTL then LTR)
snapshots/{android,ios,ios-26,mac,windows}/FlyoutPage*.png Snapshot baselines Added for all platforms

Approach Summary

Author adds UpdateFlowDirection() which:

  • Reads IsRTL (already defined in renderer) → maps to UISemanticContentAttribute.ForceRightToLeft / ForceLeftToRight.
  • Sets the attribute on View, _flyoutController.View, _detailController.View.
  • If Detail.Handler is an IPlatformViewHandler whose ViewController is a
    UINavigationController, sets the attribute on its View and NavigationBar.
  • If Detail is a NavigationPage, iterates the NavigationStack and calls
    pageHandler.UpdateValue(nameof(IView.FlowDirection)) for each page so descendants
    re-resolve direction.
    Hooks the call from ViewDidLoad (after UpdateFlyoutPageContainers) and from
    HandlePropertyChanged for FlowDirectionProperty (also re-runs UpdateLeftBarButton).

Code-Review Findings (initial pass — to be expanded by maui-expert-reviewer)

  • ⚠️ Detail container vs. detail handler view. Setting _detailController.View.SemanticContentAttribute
    is correct for the wrapper, but the inner navigation controller's view is reached via the
    Detail.Handler path. A safer pattern would also propagate to the detail's platform view
    even when Detail is NOT a NavigationPage (e.g., a plain ContentPage) so single-page
    detail content still flips. The current code only flips the top-level wrapper for that case.
  • ⚠️ Detail re-assignment. When Detail is replaced at runtime (handled in
    UpdateFlyoutPageContainers via the "Flyout"/"Detail" branch in HandlePropertyChanged),
    the new Detail.Handler does not get its SemanticContentAttribute set. The new path
    should also call UpdateFlowDirection().
  • ⚠️ NavigationStack snapshot. Iterating Navigation.NavigationStack only covers pages
    pushed at the time of the call. Pages pushed later (after ViewDidLoad) won't get the manual
    UpdateValue; they rely on standard propagation working through NavigationPage. Worth
    verifying whether the same issue reproduces after a Push.
  • ⚠️ FlyoutPage.Detail null-guard. The new code dereferences
    FlyoutPage.Detail.Handler without a null-check. Detail can momentarily be null during
    Flyout/Detail swaps; the property accessor FlyoutPage already returns (FlyoutPage)Element.
  • 💡 Style. All other Update* methods in this renderer are private/instance and don't
    explicitly null-guard; matches existing pattern. ✅
  • 💡 Test ordering. UI tests use [Order(1)]/[Order(2)] because the second test depends on
    the IsPresented state from the first. Acceptable; matches existing UITest pattern for stateful
    flyout tests.

Gate Result (passed-in)

✅ PASSED — tests FAIL without fix, PASS with fix (provided in the prompt; gate phase not re-run).

Risk Assessment

  • Blast radius: Single iOS-only renderer used by FlyoutPage in compatibility mode (not
    Shell). Behavior change is additive (a previously no-op direction now flips correctly).
  • Regression risk: Low for LTR (the new else branch sets ForceLeftToRight which
    matches default behavior). Some risk for apps that intentionally set Unspecified on the
    flyout/detail views from outside MAUI; vanishingly small.
  • API surface: No public API changes.
  • Test coverage: Snapshot tests on all 5 platforms; covers initial RTL + runtime LTR switch.
    Does NOT explicitly cover RTL→LTR→RTL toggling more than once, or Detail re-assignment.

Decision Inputs for Phases 2 & 3

  • The PR's logic is plausible and locally consistent with the rest of the renderer.
  • Worth probing in candidates: (a) Detail re-assignment, (b) navigation push after init,
    (c) using the existing EffectiveFlowDirectionExtensions.ToUISemanticContentAttribute()
    helper (if available) instead of hand-rolling, (d) using ViewExtensions.UpdateFlowDirection
    helper if applicable, (e) collapsing both paths into a recursive walk.

🔧 Fix — Analysis & Comparison

Try-Fix Aggregate — PR #34831

Phase 2 generated 4 independent candidate fixes, each loaded with a different maui-expert-reviewer domain lens to maximize diversity. None were tested on a real iOS device (no provisioned environment in this agent), so test verification is static-only. The PR's own fix already passed the gate.

# Lens Model Approach Δ Lines Test Result Notes
1 handler-patterns claude-opus-4.6 Reuse canonical UIView.UpdateFlowDirection(IView) extension; delegate child propagation by calling Detail.Handler.UpdateValue(nameof(IView.FlowDirection)); no manual NavigationStack walk; no explicit container view writes. ~+22 ⚠️ NOT RUN (static only) Smallest surface. Risk: relies on NavigationPage handler's mapper to flip UINavigationBar, which (UIKit-wise) does NOT inherit from superview — may miss the hamburger button.
2 navigation claude-sonnet-4.6 Adds ApplyDetailNavFlowDirection() plus subscribes to NavigationPage.Pushed/Popped/PoppedToRoot so pages pushed after initial load are mirrored. Hooks ViewDidLoad, ViewWillAppear, Detail swap, FlowDirection change, and Dispose. ~+95 ⚠️ NOT RUN (static only) Most robust for runtime navigation churn. Larger surface; introduces new field _trackedNavigationPage and event lifecycle.
3 platform-specifics-iOS gpt-5.3-codex Reuse canonical UIView.UpdateFlowDirection(IView); do the work inside the existing LayoutChildren() (which already runs on every layout pass and consults IsRTL); set NavigationBar.SemanticContentAttribute explicitly with one line; trigger via LayoutChildren(false) on FlowDirection change. ~+15 ⚠️ NOT RUN (static only) Most concise. Concern: runs on every layout pass (cost is tiny but non-zero); doesn't address Detail swap.
4 accessibility-RTL / API-design gpt-5.4 Recursive ApplyFlowDirectionRecursive(UIViewController) walker over ChildViewControllers; reads IFlowDirectionController.EffectiveFlowDirection; covers nested NavigationPage / TabbedPage; triggered from ViewDidLoad, FlowDirection change, AND Flyout/Detail swap (also re-runs LayoutChildren + UpdateLeftBarButton). ~+62 ⚠️ NOT RUN (static only) Most thorough; handles arbitrary nesting & Detail swap. Some redundant work (recursion can re-touch views already updated by parent).

Cross-Pollination

Cross-pollination round skipped — environment cannot run iOS device tests, so empirical comparison across the four candidates is not possible. The four lenses already produced distinct enough designs (canonical-extension-reuse / navigation-events / layout-pass-piggyback / recursive-walker) that the diversity goal is met without further iteration.

Selected Fix

Selected: pr-plus-reviewer (see report/content.md for the full comparison).

Rationale: only pr and pr-plus-reviewer have empirical regression-test evidence (gate ✅). Per the orchestrator rule, candidates that lack passed regression tests must rank lower than candidates that have them. pr-plus-reviewer is a strict superset of pr that addresses the major regression risk surfaced by the expert reviewer (Detail/Flyout swap silently dropping RTL — see inline-findings.json finding #1) plus null-safety hardening and trailing-newline fixes. The four try-fix candidates would each need a device run before they could be selected over a verified PR fix.


📋 Report — Final Recommendation

PR #34831 — Comparative Report

Title: [iOS/Mac] Fix FlyoutPage RTL FlowDirection is not working
Issue: #34830 (partner/syncfusion, s/verified)
Base: main @ b71adea6e6
Platform under review: iOS / MacCatalyst
Gate (passed-in): ✅ PASSED — tests FAIL without fix, PASS with fix.

Candidate Catalogue

Candidate Source Approach (one line) Test status Files touched
pr PR #34831 head Hand-rolled UpdateFlowDirection() setting SemanticContentAttribute on root + flyout/detail container views + nav controller/bar; walks NavigationStack calling UpdateValue("FlowDirection"); subscribes to FlowDirection change. ✅ PASSED (Gate) 1 prod + 2 tests + 7 snapshots
pr-plus-reviewer PR + maui-expert-reviewer findings applied Same as pr, plus: re-call UpdateFlowDirection() on Flyout/Detail swap; null-safe FlyoutPage?.Detail?.Handler; defensive null-checks on _flyoutController?.View/_detailController?.View; trailing-newline fix in both test files. ✅ Inherits Gate (strict superset, no behavior change for the gate scenario) 1 prod + 2 tests + 7 snapshots
try-fix-1 claude-opus-4.6 (handler-patterns) Reuse canonical UIView.UpdateFlowDirection(IView); delegate to Detail.Handler.UpdateValue("FlowDirection"); no manual nav-stack walk. ⚠️ NOT RUN 1 prod
try-fix-2 claude-sonnet-4.6 (navigation) Adds Pushed/Popped event wiring on the inner NavigationPage so pages pushed after init are also mirrored; reapplies on ViewWillAppear, Detail swap, dispose. ⚠️ NOT RUN 1 prod
try-fix-3 gpt-5.3-codex (platform-specifics-iOS) Piggy-back on LayoutChildren(): call canonical UpdateFlowDirection on the three container views and set NavigationBar.SemanticContentAttribute explicitly. ⚠️ NOT RUN 1 prod
try-fix-4 gpt-5.4 (accessibility-RTL / API-design) Recursive ApplyFlowDirectionRecursive(UIViewController) walker; reads IFlowDirectionController.EffectiveFlowDirection; triggers on swap + property change. ⚠️ NOT RUN 1 prod

Comparative Matrix

Legend: ✅ handled, ⚠️ partial, ❌ not handled, n/a not applicable.

Concern pr pr-plus-reviewer try-fix-1 try-fix-2 try-fix-3 try-fix-4
Initial RTL on ViewDidLoad
Runtime FlowDirection toggle
UINavigationBar mirrors (does NOT inherit from superview) ✅ explicit ✅ explicit ⚠️ relies on NavigationPage handler mapper ✅ explicit ✅ explicit ✅ explicit (via UINavigationController branch)
Pages already on NavigationStack get FlowDirection re-resolve ⚠️ via Handler.UpdateValue (likely) ⚠️ relies on standard propagation ✅ via recursion into ChildViewControllers
Pages pushed after init ⚠️ depends on Push triggering mapper ✅ Pushed/Popped events ⚠️ next layout pass only if it re-recurses
Detail (or Flyout) swap at runtime ❌ (major) ✅ explicit re-call ✅ via Detail-swap branch + re-wire ✅ explicit branch
Null-safety on Detail/handler dereference ✅ uses ?. chain ✅ uses ?. chain
Reuses canonical UIView.UpdateFlowDirection helper
Empirical regression-test evidence ✅ Gate ✅ Gate (strict superset)
Code surface (Δ lines vs base, prod only) +49 +64 +22 +95 +15 +62

Reviewer Inline Findings (4)

From inline-findings.json — all against PhoneFlyoutPageRenderer.cs and the new test files:

  1. major :347Flyout/Detail swap rebuilds child view controllers; the new containers/UINavigationBar start with Unspecified, so RTL is silently lost on Detail swap (very common in master-detail apps). Fix: call UpdateFlowDirection() + UpdateLeftBarButton() after UpdateFlyoutPageContainers().
  2. moderate :392, :403FlyoutPage.Detail.Handler and FlyoutPage.Detail is NavigationPage need ?. (Detail can be transiently null during reassignment / teardown); UpdateLeftBarButton already uses FlyoutPage?.Detail?.Handler.
  3. minor :386–387_flyoutController.View / _detailController.View dereferenced unconditionally; mirror surrounding defensive style with a null-guard.
  4. minor Issue34830.cs:70 and Issue34830.cs:36 — missing trailing newline; CI dotnet format flags this.

Selection

Winner: pr-plus-reviewer

  • It is the only candidate that both (a) has empirical regression-test evidence (it is a strict superset of pr, which passed the gate) and (b) addresses the major regression risk surfaced by the expert reviewer — Detail/Flyout swap silently losing RTL.
  • The four try-fix-* candidates explore reasonable alternatives, but none of them have a verified test pass on iOS (no device environment in this orchestrator). Per the orchestrator rule "candidates that failed regression tests MUST be ranked lower than candidates that passed them" we treat NOT RUN as not-passed for ranking purposes.
  • try-fix-1/try-fix-3's reuse of the canonical UIView.UpdateFlowDirection(IView) helper is architecturally cleaner; that direction would be a good follow-up refactor, but it is risk for a partner-reported bug fix that ships imminently. Recommend filing a follow-up issue to migrate this renderer onto the canonical helper.
  • try-fix-2's push/pop subscription is a real gap in pr-plus-reviewer (pages pushed after init still rely on standard propagation). Recommend tracking as a follow-up issue.
  • try-fix-4's recursive walker is elegant but introduces redundant work and changes the call shape; not worth taking over the targeted reviewer-applied changes for an in-flight bug fix.

Rank order

  1. pr-plus-reviewer — Verified by gate (strict superset of pr); fixes 1 major + 2 minor + 1 style finding. Ship this.
  2. pr — Verified by gate, but leaves the major Detail-swap regression unaddressed.
  3. try-fix-2 — Best coverage of post-init navigation events, but unverified.
  4. try-fix-4 — Most thorough propagation, unverified.
  5. try-fix-1 — Cleanest reuse of canonical helper, unverified, depends on NavigationPage handler mapper to flip UINavigationBar.
  6. try-fix-3 — Smallest, but ties FlowDirection update to layout-pass cadence and ignores Detail swap; unverified.

Recommended action

Adopt pr-plus-reviewer (apply the four reviewer findings to the PR before merge). Open follow-ups for: (a) post-init Pushed/Popped mirroring (try-fix-2 angle), (b) refactor onto canonical UIView.UpdateFlowDirection(IView) helper (try-fix-1/3 angle).


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 — 4 findings

See inline comments for details.

// UINavigationBar is a UIKit-only view outside the MAUI hierarchy and does *not*
// inherit SemanticContentAttribute from its parent. Set it explicitly so that bar
// button items (the flyout hamburger icon) are mirrored to the correct side for RTL.
if (FlyoutPage.Detail.Handler is IPlatformViewHandler detailPlatformHandler &&
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.

[moderate] Null SafetyFlyoutPage.Detail.Handler and FlyoutPage.Detail is NavigationPage (line 403) dereference Detail without a null check. FlyoutPage.Detail can be transiently null (e.g. while the user is reassigning Detail, or when UpdateFlowDirection is reached during teardown after the Detail page has been cleared). The early if (Element is null) return; guard does not cover this. Use FlyoutPage?.Detail?.Handler is IPlatformViewHandler ... and FlyoutPage?.Detail is NavigationPage ... to match the defensive style of UpdateLeftBarButton() at line 638 which already does FlyoutPage?.Detail?.Handler.

// looking at their parent's platform view find the correct direction.
View.SemanticContentAttribute = semanticAttr;
_flyoutController.View.SemanticContentAttribute = semanticAttr;
_detailController.View.SemanticContentAttribute = semanticAttr;
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.

[minor] Null Safety_flyoutController.View / _detailController.View are dereferenced unconditionally. They are normally non-null after SetElement, but UpdateFlowDirection is now reachable from HandlePropertyChanged (FlowDirection change) which can fire before ViewDidLoad has packed the containers in some lifecycle paths, and from Dispose paths if the Element raises one final property change. A cheap if (_flyoutController?.View is not null) / if (_detailController?.View is not null) guard mirrors the rest of this renderer's defensive style and avoids a hard NRE for an RTL-only code path that has historically had no protection.

}
});
}
} 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.

[minor] Style — File is missing a trailing newline (\ No newline at end of file in the diff). The repo convention everywhere else under src/Controls/tests/TestCases.HostApp/Issues/ is to end with a newline; CI dotnet format typically flags this. Same issue in src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34830.cs:36.

@MauiBot MauiBot added s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels May 8, 2026
@kubaflo kubaflo changed the base branch from main to inflight/current May 8, 2026 11:00
@kubaflo kubaflo merged commit b6959f4 into dotnet:inflight/current May 8, 2026
37 of 38 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR7 milestone May 8, 2026
@kubaflo kubaflo added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-review-incomplete labels May 20, 2026
PureWeen pushed a commit that referenced this pull request Jun 2, 2026
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
In FlyoutPage, RTL FlowDirection is not working properly at initial and
runtime changes.

### Description of Changes
* Added an `UpdateFlowDirection` method to set the correct
`SemanticContentAttribute` on the root view, child controller views, and
navigation bar, ensuring proper mirroring for RTL layouts and correct
inheritance by child handlers.
* Updated property change handling to listen for `FlowDirection` changes
and trigger both `UpdateFlowDirection` and `UpdateLeftBarButton` when
flow direction is updated at runtime.
* Called `UpdateFlowDirection` during gesture recognizer setup to ensure
the correct flow direction is applied during initialization.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #34830 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/f9a9cc1a-3320-4e8b-9c6c-3702c585b5c2"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/7da0ba09-557f-46bd-81b7-2fed1295b61d"
width="300" height="600"> |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-flyoutpage FlyoutPage community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/ios platform/macos macOS / Mac Catalyst s/agent-fix-win AI found a better alternative fix than the PR s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS/Mac] FlyoutPage RTL FlowDirection is not working properly

6 participants