Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ bool shouldReceive(UIGestureRecognizer g, UITouch t)

PackContainers();
UpdateFlyoutPageContainers();
UpdateFlowDirection();

UpdateBackground();

UpdatePanGesture();
UpdateApplyShadow(((FlyoutPage)Element).OnThisPlatform().GetApplyShadow());
UpdatePageSpecifics();
Expand Down Expand Up @@ -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();
}
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
}

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


// 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.

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)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 70 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue34830.cs
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
});
}
}
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.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading