diff --git a/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj b/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
index 58d2c6ca3ed9..074098a2858c 100644
--- a/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
+++ b/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
@@ -10,6 +10,7 @@
maccatalyst-x64maccatalyst-arm64true
+ Maui.Controls.Sample
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue24246.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue24246.xaml
new file mode 100644
index 000000000000..400553f8c34b
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue24246.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue24246.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue24246.xaml.cs
new file mode 100644
index 000000000000..6a2fa194133a
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue24246.xaml.cs
@@ -0,0 +1,11 @@
+namespace Maui.Controls.Sample.Issues;
+
+
+[Issue(IssueTracker.Github, 24246, "SafeArea arrange insets are currently insetting based on an incorrect Bounds", PlatformAffected.iOS)]
+public partial class Issue24246 : ContentPage
+{
+ public Issue24246()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24246.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24246.cs
new file mode 100644
index 000000000000..d23493671acd
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24246.cs
@@ -0,0 +1,26 @@
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues
+{
+ public class Issue24246 : _IssuesUITest
+ {
+ public Issue24246(TestDevice testDevice) : base(testDevice)
+ {
+ }
+
+ public override string Issue => "SafeArea arrange insets are currently insetting based on an incorrect Bounds";
+
+ [Test]
+ [Category(UITestCategories.Layout)]
+ public void TapThenDoubleTap()
+ {
+ App.WaitForElement("entry");
+ App.EnterText("entry", "Hello, World!");
+
+ var result = App.WaitForElement("entry").GetText();
+ Assert.That(result, Is.EqualTo("Hello, World!"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Core/src/Platform/iOS/MauiView.cs b/src/Core/src/Platform/iOS/MauiView.cs
index e125a46f5586..afdf1f86b097 100644
--- a/src/Core/src/Platform/iOS/MauiView.cs
+++ b/src/Core/src/Platform/iOS/MauiView.cs
@@ -25,9 +25,16 @@ public IView? View
bool RespondsToSafeArea()
{
+ if (View is not ISafeAreaView sav || sav.IgnoreSafeArea)
+ {
+ return false;
+ }
+
if (_respondsToSafeArea.HasValue)
return _respondsToSafeArea.Value;
+
return (bool)(_respondsToSafeArea = RespondsToSelector(new Selector("safeAreaInsets")));
+
}
protected CGRect AdjustForSafeArea(CGRect bounds)
@@ -35,7 +42,7 @@ protected CGRect AdjustForSafeArea(CGRect bounds)
if (KeyboardAutoManagerScroll.ShouldIgnoreSafeAreaAdjustment)
KeyboardAutoManagerScroll.ShouldScrollAgain = true;
- if (View is not ISafeAreaView sav || sav.IgnoreSafeArea || !RespondsToSafeArea())
+ if (!RespondsToSafeArea())
{
return bounds;
}
@@ -88,6 +95,12 @@ Size CrossPlatformArrange(Rect bounds)
return CrossPlatformLayout?.CrossPlatformArrange(bounds) ?? Size.Zero;
}
+ // SizeThatFits does not take into account the constraints set on the view.
+ // For example, if the user has set a width and height on this view, those constraints
+ // will not be reflected in the value returned from this method. This method purely returns
+ // a measure based on the size that is passed in.
+ // The constraints are all applied by ViewHandlerExtensions.GetDesiredSizeFromHandler
+ // after it calls this method.
public override CGSize SizeThatFits(CGSize size)
{
if (_crossPlatformLayoutReference == null)
@@ -102,6 +115,23 @@ public override CGSize SizeThatFits(CGSize size)
CacheMeasureConstraints(widthConstraint, heightConstraint);
+ if (RespondsToSafeArea())
+ {
+ // During the LayoutSubViews pass, we adjust the Bounds of this view for the safe area and then pass the adjusted result to CrossPlatformArrange.
+ // The CrossPlatformMeasure call does not include the safe area, so we need to add it here to ensure the returned size is correct.
+ //
+ // For example, if this is a layout with an Entry of height 20, CrossPlatformMeasure will return a height of 20.
+ // This means the bounds will be set to a height of 20, causing AdjustForSafeArea(Bounds) to return a negative bounds once it has
+ // subtracted the safe area insets. Therefore, we need to add the safe area insets to the CrossPlatformMeasure result to ensure correct arrangement.
+ var widthSafeAreaOffset = SafeAreaInsets.Left + SafeAreaInsets.Right;
+ var heightSafeAreaOffset = SafeAreaInsets.Top + SafeAreaInsets.Bottom;
+
+ var width = double.Clamp(crossPlatformSize.Width + widthSafeAreaOffset, 0, widthConstraint);
+ var height = double.Clamp(crossPlatformSize.Height + heightSafeAreaOffset, 0, heightConstraint);
+
+ return new CGSize(width, height);
+ }
+
return crossPlatformSize.ToCGSize();
}