Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Comment on lines +374 to +375

Copilot AI Dec 9, 2025

Copy link

Choose a reason for hiding this comment

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

The condition Element is FlyoutPage flyoutPage is redundant. Since FlyoutPage is a property that already casts Element to FlyoutPage, and FlyoutPage inherits from Page which implements ISafeAreaView, you can simplify this to:

if (FlyoutPage is ISafeAreaView sav && !sav.IgnoreSafeArea && OperatingSystem.IsIOSVersionAtLeast(11))

This eliminates the unnecessary pattern matching and uses the existing FlyoutPage property.

Suggested change
if (Element is FlyoutPage flyoutPage && flyoutPage is ISafeAreaView sav &&
!sav.IgnoreSafeArea && OperatingSystem.IsIOSVersionAtLeast(11))
if (FlyoutPage is ISafeAreaView sav && !sav.IgnoreSafeArea && OperatingSystem.IsIOSVersionAtLeast(11))

Copilot uses AI. Check for mistakes.
{
var safeAreaInsets = View.SafeAreaInsets;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

var safeAreaTop = safeAreaInsets.Top;

safeAreaInsets.Top is checked twice, can extract it into a local variable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated the PR with the recommended code optimization by extracting safeAreaInsets.Top into a local variable to reduce property access and improve readability.

if (safeAreaInsets.Top > 0)
{
frame.Y = safeAreaInsets.Top;
frame.Height -= safeAreaInsets.Top;
}
}
Comment on lines +374 to +384

Copilot AI Dec 9, 2025

Copy link

Choose a reason for hiding this comment

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

The safe area adjustment in LayoutChildren is not reactive to changes in the UseSafeArea property. If a developer dynamically changes ios:Page.UseSafeArea at runtime, the layout won't update until the next time LayoutChildren is called for other reasons.

Consider adding a property changed handler in HandlePropertyChanged method:

else if (e.PropertyName == PlatformConfiguration.iOSSpecific.Page.UseSafeAreaProperty.PropertyName)
    LayoutChildren(false);

This ensures that toggling the safe area setting at runtime immediately updates the layout.

Copilot uses AI. Check for mistakes.

var flyoutFrame = frame;
nfloat opacity = 1;

Expand Down
76 changes: 76 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue29170.cs
Original file line number Diff line number Diff line change
@@ -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<iOS>().SetUseSafeArea(true);
}

private void ToggleFlyoutMenu(object sender, EventArgs e) => IsPresented = !IsPresented;
}
Original file line number Diff line number Diff line change
@@ -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()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This test is failing on iOS:

CollectionView Y position should be at least 69 to avoid overlapping with safe area
Assert.That(App.WaitForElement("CollectionView").GetRect().Y, Is.GreaterThanOrEqualTo(69))
Expected: greater than or equal to 69
But was:  54

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@jsuarezruiz , now the test case has been updated with proper assert conditions that correctly verify safe area respecting behavior on iOS devices.

{
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