Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -310,31 +310,40 @@ void LayoutHeader(CGRect parentFrame)

void LayoutContent(CGRect parentBounds, nfloat footerHeight)
{
double contentYOffset = 0;
var safeAreaInsets = UIApplication.SharedApplication.GetSafeAreaInsetsForWindow();

if (ShouldHonorSafeArea(HeaderView?.View) ||
(HeaderView is null && ShouldHonorSafeArea(Content)))
// Honor ISafeAreaView.IgnoreSafeArea and explicit margins (same as LayoutHeader)
nfloat safeAreaTop = 0;
if (ShouldHonorSafeArea(HeaderView?.View) || (HeaderView is null && ShouldHonorSafeArea(Content)))
{
// We add the safe area if margin is not explicitly set. This matches the header behavior.
contentYOffset += (float)UIApplication.SharedApplication.GetSafeAreaInsetsForWindow().Top;
safeAreaTop = safeAreaInsets.Top;
}
nfloat safeAreaBottom = safeAreaInsets.Bottom;

var contentY = parentBounds.Y + safeAreaTop;
var contentHeight = parentBounds.Height - safeAreaTop - safeAreaBottom - footerHeight;

if (HeaderView is not null)
{
if (ScrollView is null)
{
// The margin is already managed by MAUI's layout system, so we don't need to add it here and we just offset the content by the header's height.
contentYOffset += HeaderView.Frame.Height;
// The margin is already managed by MAUI's layout system, so we don't need to add it here
// and we just offset the content by the header's height.
contentY += HeaderView.Frame.Height;
contentHeight -= HeaderView.Frame.Height;
}
else
{
// For ScrollView, we need to consider the margin, but we should not consider the header height, since it should overlap with the scroll view.
// The content inset is already managed by SetHeaderContentInset.
contentYOffset += HeaderView.View.Margin.VerticalThickness;
// For ScrollView, we should not offset by the full header height since the scroll view
// overlaps with the header (FlyoutHeaderBehavior.Scroll / CollapseOnScroll parallax).
// The top content inset is managed by SetHeaderContentInset; only account for margin here.
var marginOffset = (nfloat)HeaderView.View.Margin.VerticalThickness;
contentY += marginOffset;
contentHeight -= marginOffset;
}
}

var contentFrame = new Rect(parentBounds.X, contentYOffset, parentBounds.Width, parentBounds.Height - contentYOffset - footerHeight);
var contentFrame = new Rect(parentBounds.X, contentY, parentBounds.Width, contentHeight);
if (Content is null)
{
ContentView.Frame = contentFrame.AsCGRect();
Expand Down
6 changes: 6 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue32275.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 32275, "Shell Flyout SafeArea Rendering", PlatformAffected.iOS)]
public class Issue32275 : ShellFlyoutContentBase
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.None, 0, "Shell Flyout Content",
PlatformAffected.All)]
public class ShellFlyoutContent : ShellFlyoutContentBase
{
}

public class ShellFlyoutContent : TestShell
// Base class with shared Init logic. No [Issue] attribute here so subclasses
// can each declare their own without triggering AmbiguousMatchException.
public abstract class ShellFlyoutContentBase : TestShell
{
protected override void Init()
{
Expand Down
100 changes: 100 additions & 0 deletions src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32275.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# if IOS //More info : https://github.com/dotnet/maui/pull/34510
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue32275 : _IssuesUITest
{
const string FlyoutItem = "FlyoutItem";
const string ResetButton = "Reset";
public override string Issue => "Shell Flyout SafeArea Rendering";

protected override bool ResetAfterEachTest => true;

public Issue32275(TestDevice device) : base(device) { }

// Test 1: Open flyout with default items and capture screenshot
[Test, Order(1)]
[Category(UITestCategories.SafeAreaEdges)]
public void VerifyDefaultFlyoutItemsRendering()
{
App.WaitForElement("PageLoaded");
App.ShowFlyout();
VerifyScreenshot();
}

// Test 2: Open flyout with header/footer and capture screenshot
[Test, Order(2)]
[Category(UITestCategories.SafeAreaEdges)]
public void VerifyFlyoutWithHeaderFooter()
{
App.WaitForElement("ToggleHeaderFooter");
App.Tap("ToggleHeaderFooter");
App.WaitForElement("PageLoaded");
App.ShowFlyout();
App.WaitForElement("Header View");
App.WaitForElement("Footer View");
VerifyScreenshot();
}

// Test 3: Tap ToggleFlyoutContentTemplate, then open flyout and capture screenshot
[Test, Order(3)]
[Category(UITestCategories.SafeAreaEdges)]
public void VerifyCustomFlyoutContentTemplateRendering()
{
App.WaitForElement("ToggleFlyoutContentTemplate");
App.Tap("ToggleFlyoutContentTemplate");
App.WaitForElement("PageLoaded");
App.ShowFlyout();
VerifyScreenshot();
}

// Test 4: ToggleFlyoutContentTemplate + header/footer, open flyout and capture screenshot
[Test, Order(4)]
[Category(UITestCategories.SafeAreaEdges)]
public void VerifyCustomFlyoutContentTemplateWithHeaderFooter()
{
App.WaitForElement("ToggleFlyoutContentTemplate");
App.Tap("ToggleFlyoutContentTemplate");
App.WaitForElement("ToggleHeaderFooter");
App.Tap("ToggleHeaderFooter");
App.WaitForElement("PageLoaded");
App.ShowFlyout();
App.WaitForElement("Header View");
App.WaitForElement("Footer View");
VerifyScreenshot();
}

// Test 5: Tap Toggle Flyout Content, open flyout, capture screenshot
[Test, Order(5)]
[Category(UITestCategories.SafeAreaEdges)]
public void VerifyCustomFlyoutContentRendering()
{
App.WaitForElement("ToggleContent");
App.Tap("ToggleContent");
App.WaitForElement("PageLoaded");
App.ShowFlyout();
App.WaitForElement("ContentView");
VerifyScreenshot();
}

// Test 6: Toggle Flyout Content + header/footer, open flyout, capture screenshot
[Test, Order(6)]
[Category(UITestCategories.SafeAreaEdges)]
public void VerifyCustomFlyoutContentWithHeaderFooter()
{
App.WaitForElement("ToggleContent");
App.Tap("ToggleContent");
App.WaitForElement("ToggleHeaderFooter");
App.Tap("ToggleHeaderFooter");
App.WaitForElement("PageLoaded");
App.ShowFlyout();
App.WaitForElement("ContentView");
App.WaitForElement("Header View");
App.WaitForElement("Footer View");
VerifyScreenshot();
}
}
#endif
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
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading