From fb7effa67df0eb7520a737e5d6c00fa3243c7e23 Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Tue, 29 Apr 2025 10:14:12 +0530 Subject: [PATCH 1/7] Update PhoneFlyoutPageRenderer.cs --- .../FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 8cf5c209b799..c30380d4404b 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs @@ -374,6 +374,19 @@ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) void LayoutChildren(bool animated) { var frame = Element.Bounds.ToCGRect(); + + if (Element is FlyoutPage flyoutPage && flyoutPage is ISafeAreaView sav && + !sav.IgnoreSafeArea && OperatingSystem.IsIOSVersionAtLeast(11)) + { + var safeAreaInsets = View.SafeAreaInsets; + + if (safeAreaInsets.Top > 0) + { + frame.Y = safeAreaInsets.Top; + frame.Height -= safeAreaInsets.Top; + } + } + var flyoutFrame = frame; nfloat opacity = 1; From c09d4b8de0332a167561e8529f6da65be5f56fac Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Tue, 29 Apr 2025 18:29:33 +0530 Subject: [PATCH 2/7] updating testcase to the fix --- .../TestCases.HostApp/Issues/Issue29170.cs | 76 +++++++++++++++++++ .../Tests/Issues/Issue29170.cs | 23 ++++++ 2 files changed, 99 insertions(+) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue29170.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue29170.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue29170.cs new file mode 100644 index 000000000000..3603e298dd07 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue29170.cs @@ -0,0 +1,76 @@ +namespace Maui.Controls.Sample.Issues; + +using System; +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Xaml; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Controls.PlatformConfiguration; +using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; + +[XamlCompilation(XamlCompilationOptions.Compile)] +[Issue(IssueTracker.Github, 29170, "First Item in CollectionView Overlaps in FlyoutPage.Flyout on iOS", PlatformAffected.iOS)] +public partial class Issue29170 : Microsoft.Maui.Controls.FlyoutPage +{ + public Issue29170() + { + var flyoutPage = new ContentPage + { + Title = "Menu", + Content = new CollectionView + { + ItemsSource = new[] { "Item 1", "Item 2", "Item 3", "Item 4" }, + AutomationId = "CollectionView", + ItemSizingStrategy = ItemSizingStrategy.MeasureAllItems, + Margin = 10, + ItemTemplate = new DataTemplate(() => + { + var titleLabel = new Label + { + FontSize = 32, + LineBreakMode = LineBreakMode.TailTruncation + }; + titleLabel.SetBinding(Label.TextProperty, new Binding(".")); + + var subHeaderLabel = new Label + { + FontSize = 16, + Opacity = 0.66, + LineBreakMode = LineBreakMode.TailTruncation, + Text = "subheader" + }; + + return new VerticalStackLayout + { + Padding = new Thickness(5), + Children = { titleLabel, subHeaderLabel } + }; + }) + } + }; + + Flyout = flyoutPage; + + var toggleButton = new Button + { + Text = "Open Flyout Menu", + FontSize = 24, + AutomationId = "FlyoutButton", + Margin = 10, + WidthRequest = 220, + HeightRequest = 50, + VerticalOptions = LayoutOptions.Start, + BackgroundColor = Colors.Blue + }; + toggleButton.Clicked += ToggleFlyoutMenu; + + Detail = new ContentPage + { + Content = toggleButton + }; + + this.On().SetUseSafeArea(true); + } + + private void ToggleFlyoutMenu(object sender, EventArgs e) => IsPresented = !IsPresented; +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs new file mode 100644 index 000000000000..b044b92336ab --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs @@ -0,0 +1,23 @@ +#if IOS +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +internal class Issue29170 : _IssuesUITest +{ + public Issue29170(TestDevice device) : base(device) { } + + public override string Issue => "First Item in CollectionView Overlaps in FlyoutPage.Flyout on iOS"; + + [Test] + [Category(UITestCategories.FlyoutPage)] + public void CollectionViewFirstItemShouldNotOverlapWithSafeAreaInFlyoutMenu() + { + App.WaitForElement("FlyoutButton").Tap(); + Assert.That(App.WaitForElement("CollectionView").GetRect().Y, Is.GreaterThanOrEqualTo(69), + "CollectionView Y position should be at least 69 to avoid overlapping with safe area"); + } +} +#endif From dc11ec045dc94bc61171db29a8bfd6764967c8b2 Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Fri, 2 May 2025 12:48:53 +0530 Subject: [PATCH 3/7] updated test case --- .../TestCases.HostApp/Issues/Issue29170.cs | 77 ++++++++++--------- .../Tests/Issues/Issue29170.cs | 9 ++- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue29170.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue29170.cs index 3603e298dd07..633abcd07105 100644 --- a/src/Controls/tests/TestCases.HostApp/Issues/Issue29170.cs +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue29170.cs @@ -14,42 +14,11 @@ public partial class Issue29170 : Microsoft.Maui.Controls.FlyoutPage { public Issue29170() { - var flyoutPage = new ContentPage + Flyout = new ContentPage { Title = "Menu", - Content = new CollectionView - { - ItemsSource = new[] { "Item 1", "Item 2", "Item 3", "Item 4" }, - AutomationId = "CollectionView", - ItemSizingStrategy = ItemSizingStrategy.MeasureAllItems, - Margin = 10, - ItemTemplate = new DataTemplate(() => - { - var titleLabel = new Label - { - FontSize = 32, - LineBreakMode = LineBreakMode.TailTruncation - }; - titleLabel.SetBinding(Label.TextProperty, new Binding(".")); - - var subHeaderLabel = new Label - { - FontSize = 16, - Opacity = 0.66, - LineBreakMode = LineBreakMode.TailTruncation, - Text = "subheader" - }; - - return new VerticalStackLayout - { - Padding = new Thickness(5), - Children = { titleLabel, subHeaderLabel } - }; - }) - } + Content = CreateCollectionView("CollectionViewFlyout") }; - - Flyout = flyoutPage; var toggleButton = new Button { @@ -59,18 +28,54 @@ public Issue29170() Margin = 10, WidthRequest = 220, HeightRequest = 50, - VerticalOptions = LayoutOptions.Start, + HorizontalOptions = LayoutOptions.Center, BackgroundColor = Colors.Blue }; toggleButton.Clicked += ToggleFlyoutMenu; + var detailCollectionView = CreateCollectionView("CollectionViewDetail"); + detailCollectionView.Footer = toggleButton; + Detail = new ContentPage { - Content = toggleButton + Content = detailCollectionView }; this.On().SetUseSafeArea(true); } + + private CollectionView CreateCollectionView(string automationId) + { + return new CollectionView + { + ItemsSource = new[] { "Item 1", "Item 2", "Item 3", "Item 4" }, + AutomationId = automationId, + Margin = 10, + ItemTemplate = new DataTemplate(() => + { + var titleLabel = new Label + { + FontSize = 32, + LineBreakMode = LineBreakMode.TailTruncation + }; + titleLabel.SetBinding(Label.TextProperty, new Binding(".")); + + var subHeaderLabel = new Label + { + FontSize = 16, + Opacity = 0.66, + LineBreakMode = LineBreakMode.TailTruncation, + Text = "subheader" + }; + + return new VerticalStackLayout + { + Padding = new Thickness(5), + Children = { titleLabel, subHeaderLabel } + }; + }) + }; + } private void ToggleFlyoutMenu(object sender, EventArgs e) => IsPresented = !IsPresented; -} +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs index b044b92336ab..5fe4258a8807 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs @@ -15,9 +15,14 @@ public Issue29170(TestDevice device) : base(device) { } [Category(UITestCategories.FlyoutPage)] public void CollectionViewFirstItemShouldNotOverlapWithSafeAreaInFlyoutMenu() { + Assert.That(App.WaitForElement("CollectionViewDetail").GetRect().Y, Is.GreaterThanOrEqualTo(54), + "CollectionView Y position should be at least 54 to avoid overlapping with safe area"); App.WaitForElement("FlyoutButton").Tap(); - Assert.That(App.WaitForElement("CollectionView").GetRect().Y, Is.GreaterThanOrEqualTo(69), - "CollectionView Y position should be at least 69 to avoid overlapping with safe area"); + Assert.That(App.WaitForElement("CollectionViewFlyout").GetRect().Y, Is.GreaterThanOrEqualTo(54), + "CollectionView Y position should be at least 54 to avoid overlapping with safe area"); } } + +//need to wait for test case to complete and then add here for flyout detail page also assert condition and then commit. +//also modoify the test value to 54 #endif From 76993bbfd3e22f070a126cee2c8f09929d4c54d1 Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Fri, 2 May 2025 12:57:11 +0530 Subject: [PATCH 4/7] Update Issue29170.cs --- .../tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs index 5fe4258a8807..615eb0b9bc9c 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs @@ -22,7 +22,4 @@ public void CollectionViewFirstItemShouldNotOverlapWithSafeAreaInFlyoutMenu() "CollectionView Y position should be at least 54 to avoid overlapping with safe area"); } } - -//need to wait for test case to complete and then add here for flyout detail page also assert condition and then commit. -//also modoify the test value to 54 #endif From dd6e383b826003ff6989c1297c66fcf7e52ac961 Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Fri, 2 May 2025 14:59:09 +0530 Subject: [PATCH 5/7] Update Issue29170.cs --- .../tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs index 615eb0b9bc9c..b25ced13ecdf 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs @@ -1,4 +1,4 @@ -#if IOS +#if IOS //iOS only support the SetUseSafeArea using NUnit.Framework; using UITest.Appium; using UITest.Core; From 2edc82e87c6b0e8df335f7c47a08468674fe276c Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Mon, 26 May 2025 16:29:39 +0530 Subject: [PATCH 6/7] Update PhoneFlyoutPageRenderer.cs --- .../Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 c30380d4404b..5f2a4b0b2095 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs @@ -378,12 +378,12 @@ void LayoutChildren(bool animated) if (Element is FlyoutPage flyoutPage && flyoutPage is ISafeAreaView sav && !sav.IgnoreSafeArea && OperatingSystem.IsIOSVersionAtLeast(11)) { - var safeAreaInsets = View.SafeAreaInsets; + var safeAreaTop = View.SafeAreaInsets.Top; - if (safeAreaInsets.Top > 0) + if (safeAreaTop > 0) { - frame.Y = safeAreaInsets.Top; - frame.Height -= safeAreaInsets.Top; + frame.Y = safeAreaTop; + frame.Height -= safeAreaTop; } } From 3ec65e9e7e303510a4e04b16901b84fb0fc7d33f Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Mon, 16 Feb 2026 18:58:47 +0530 Subject: [PATCH 7/7] update comments and test as per AI summary --- .../FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs | 13 +++++++++---- .../Tests/Issues/Issue29170.cs | 12 ++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) 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 88ddfa51be94..3c32df43d13e 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs @@ -366,15 +366,20 @@ void LayoutChildren(bool animated) { var frame = Element.Bounds.ToCGRect(); + // Apply safe area insets to the FlyoutPage container if UseSafeArea is enabled. + // This ensures the Flyout content (e.g., CollectionView) renders below the status bar/notch + // on iOS devices with notches (iPhone X and newer). Without this adjustment, the container + // would start at Y=0, causing content to overlap with the status bar. + // https://github.com/dotnet/maui/issues/29170 if (Element is FlyoutPage flyoutPage && flyoutPage is ISafeAreaView sav && !sav.IgnoreSafeArea && OperatingSystem.IsIOSVersionAtLeast(11)) { - var safeAreaTop = View.SafeAreaInsets.Top; + var safeAreaTopInset = View.SafeAreaInsets.Top; - if (safeAreaTop > 0) + if (safeAreaTopInset > 0) { - frame.Y = safeAreaTop; - frame.Height -= safeAreaTop; + frame.Y = safeAreaTopInset; + frame.Height -= safeAreaTopInset; } } diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs index b25ced13ecdf..56fde70d4df7 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29170.cs @@ -15,11 +15,15 @@ public Issue29170(TestDevice device) : base(device) { } [Category(UITestCategories.FlyoutPage)] public void CollectionViewFirstItemShouldNotOverlapWithSafeAreaInFlyoutMenu() { - Assert.That(App.WaitForElement("CollectionViewDetail").GetRect().Y, Is.GreaterThanOrEqualTo(54), - "CollectionView Y position should be at least 54 to avoid overlapping with safe area"); + // Use 44 (status bar height) as conservative minimum instead of device-specific value + // This prevents test flakiness across different simulators and iOS versions + const int MinimumSafeAreaHeight = 44; + + Assert.That(App.WaitForElement("CollectionViewDetail").GetRect().Y, Is.GreaterThanOrEqualTo(MinimumSafeAreaHeight), + $"CollectionView Y position should be at least {MinimumSafeAreaHeight} to avoid overlapping with safe area"); App.WaitForElement("FlyoutButton").Tap(); - Assert.That(App.WaitForElement("CollectionViewFlyout").GetRect().Y, Is.GreaterThanOrEqualTo(54), - "CollectionView Y position should be at least 54 to avoid overlapping with safe area"); + Assert.That(App.WaitForElement("CollectionViewFlyout").GetRect().Y, Is.GreaterThanOrEqualTo(MinimumSafeAreaHeight), + $"CollectionView Y position should be at least {MinimumSafeAreaHeight} to avoid overlapping with safe area"); } } #endif