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 9ec7e8447a56..41de494f9209 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs @@ -186,9 +186,9 @@ bool shouldReceive(UIGestureRecognizer g, UITouch t) PackContainers(); UpdateFlyoutPageContainers(); + UpdateFlowDirection(); UpdateBackground(); - UpdatePanGesture(); UpdateApplyShadow(((FlyoutPage)Element).OnThisPlatform().GetApplyShadow()); UpdatePageSpecifics(); @@ -360,6 +360,54 @@ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) 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; + + // Determine the semantic content attribute from the MAUI model so this works + // correctly both during initial setup (when the parent UIView's attribute may not + // yet be set) and for runtime FlowDirection changes. + var semanticAttr = IsRTL + ? UISemanticContentAttribute.ForceRightToLeft + : UISemanticContentAttribute.ForceLeftToRight; + + // Set SemanticContentAttribute on the root view and both child-controller container + // views so that any MAUI child handlers that resolve FlowDirection.MatchParent by + // looking at their parent's platform view find the correct direction. + View.SemanticContentAttribute = semanticAttr; + _flyoutController.View.SemanticContentAttribute = semanticAttr; + _detailController.View.SemanticContentAttribute = semanticAttr; + + // 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 && + detailPlatformHandler.ViewController is UINavigationController navController) + { + navController.View.SemanticContentAttribute = semanticAttr; + navController.NavigationBar.SemanticContentAttribute = semanticAttr; + } + + // NavigationPage is neither IContainer nor IContentView in the Core layer, so + // PropagateFlowDirection in ViewExtensions does not recurse into its pages. + // Manually re-trigger FlowDirection on each page in the navigation stack so their + // platform views (and descendants) pick up the correct direction on initial load. + 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.Android.Tests/snapshots/android/FlyoutPageWithLTRDirection.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlyoutPageWithLTRDirection.png new file mode 100644 index 000000000000..9c16d127fff8 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlyoutPageWithLTRDirection.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlyoutPageWithRTLDirection.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlyoutPageWithRTLDirection.png new file mode 100644 index 000000000000..e55a2d5d0a0b Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlyoutPageWithRTLDirection.png differ diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue34830.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue34830.cs new file mode 100644 index 000000000000..4fba568c044e --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue34830.cs @@ -0,0 +1,70 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 34830, "[iOS/Mac] FlyoutPage RTL FlowDirection is not working properly", PlatformAffected.iOS | PlatformAffected.macOS)] +public class Issue34830 : TestFlyoutPage +{ + protected override void Init() + { + FlyoutLayoutBehavior = FlyoutLayoutBehavior.Popover; + FlowDirection = FlowDirection.RightToLeft; + + Flyout = new ContentPage + { + Title = "Flyout", + BackgroundColor = Colors.SkyBlue, + IconImageSource = "menu_icon", + Content = new StackLayout + { + Children = + { + new Button + { + Text = "If you can see me the test has passed", + AutomationId = "CloseRootView", + Command = new Command(() => IsPresented = false) + } + }, + AutomationId = "RootLayout" + }, + Padding = new Thickness(0, 42, 0, 0) + }; + + Detail = new NavigationPage(new ContentPage + { + Title = "Detail", + Content = new StackLayout + { + Children = + { + new Label + { + Text = "The page must be with RightToLeft FlowDirection. Hamburger icon in main page must be on the right side. There should be visible text inside the Flyout View" + }, + new Button + { + Text = "Set RightToLeft", + Command = new Command(() => FlowDirection = FlowDirection.RightToLeft), + AutomationId = "ShowRightToLeft" + }, + new Button + { + Text = "Set LeftToRight", + Command = new Command(() => FlowDirection = FlowDirection.LeftToRight), + AutomationId = "ShowLeftToRight" + }, + new Button + { + Text = "Open Flyout View", + Command = new Command(() => IsPresented = true), + AutomationId = "OpenRootView" + }, + new Label + { + Text = DeviceInfo.Idiom.ToString(), + AutomationId = "Idiom" + } + } + } + }); + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithLTRDirection.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithLTRDirection.png new file mode 100644 index 000000000000..f6644273e378 Binary files /dev/null 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 new file mode 100644 index 000000000000..d838a521f1d4 Binary files /dev/null and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlyoutPageWithRTLDirection.png differ diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34830.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34830.cs new file mode 100644 index 000000000000..b6dcbc1f86fc --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34830.cs @@ -0,0 +1,36 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue34830 : _IssuesUITest +{ + public Issue34830(TestDevice device) : base(device) + { + } + + public override string Issue => "[iOS/Mac] FlyoutPage RTL FlowDirection is not working properly"; + + [Test, Order(1)] + [Category(UITestCategories.FlyoutPage)] + public void FlyoutPageWithRTLDirection() + { + App.WaitForElement("ShowRightToLeft"); + App.WaitForElement("OpenRootView"); + App.Tap("OpenRootView"); + VerifyScreenshot(); + } + + [Test, Order(2)] + [Category(UITestCategories.FlyoutPage)] + public void FlyoutPageWithLTRDirection() + { + App.Tap("CloseRootView"); + App.WaitForElement("ShowLeftToRight"); + App.Tap("ShowLeftToRight"); + App.WaitForElement("OpenRootView"); + App.Tap("OpenRootView"); + VerifyScreenshot(); + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/FlyoutPageWithRTLDirection.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/FlyoutPageWithRTLDirection.png new file mode 100644 index 000000000000..81a179222600 Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/FlyoutPageWithRTLDirection.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/FlyoutPageWithLTRDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/FlyoutPageWithLTRDirection.png new file mode 100644 index 000000000000..c8c3a62edc4b Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/FlyoutPageWithLTRDirection.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/FlyoutPageWithRTLDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/FlyoutPageWithRTLDirection.png new file mode 100644 index 000000000000..1b5c27fa4bae Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/FlyoutPageWithRTLDirection.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlyoutPageWithLTRDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlyoutPageWithLTRDirection.png new file mode 100644 index 000000000000..c67242fadf65 Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlyoutPageWithLTRDirection.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlyoutPageWithRTLDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlyoutPageWithRTLDirection.png new file mode 100644 index 000000000000..e891b0b9db30 Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlyoutPageWithRTLDirection.png differ