Skip to content

Commit c86e5fb

Browse files
committed
Fix SafeArea adjustments
1 parent 7a6b1af commit c86e5fb

File tree

4 files changed

+81
-1
lines changed

4 files changed

+81
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
3+
x:Class="Maui.Controls.Sample.Issues.Issue24246"
4+
Title="Issue24246"
5+
xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls"
6+
ios:Page.UseSafeArea="false">
7+
<VerticalStackLayout Background="Purple" IgnoreSafeArea="False" VerticalOptions="Start" IsClippedToBounds="True" >
8+
<Entry AutomationId="entry" Background="Green"></Entry>
9+
<Label AutomationId="label" Text="If you can't interact with the above entry field, the test has failed" LineBreakMode="CharacterWrap"></Label>
10+
</VerticalStackLayout>
11+
</ContentPage>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Maui.Controls.Sample.Issues;
2+
3+
4+
[Issue(IssueTracker.Github, 24246, "SafeArea arrange insets are currently insetting based on an incorrect Bounds", PlatformAffected.iOS)]
5+
public partial class Issue24246 : ContentPage
6+
{
7+
public Issue24246()
8+
{
9+
InitializeComponent();
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using NUnit.Framework;
2+
using UITest.Appium;
3+
using UITest.Core;
4+
5+
namespace Microsoft.Maui.TestCases.Tests.Issues
6+
{
7+
public class Issue24246 : _IssuesUITest
8+
{
9+
public Issue24246(TestDevice testDevice) : base(testDevice)
10+
{
11+
}
12+
13+
public override string Issue => "SafeArea arrange insets are currently insetting based on an incorrect Bounds";
14+
15+
[Test]
16+
[Category(UITestCategories.Layout)]
17+
public void TapThenDoubleTap()
18+
{
19+
App.WaitForElement("entry");
20+
App.EnterText("entry", "Hello, World!");
21+
22+
var result = App.WaitForElement("entry").GetText();
23+
Assert.That(result, Is.EqualTo("Hello, World!"));
24+
}
25+
}
26+
}

src/Core/src/Platform/iOS/MauiView.cs

+33-1
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,16 @@ public IView? View
2626

2727
bool RespondsToSafeArea()
2828
{
29+
if (View is not ISafeAreaView sav || sav.IgnoreSafeArea)
30+
{
31+
return false;
32+
}
33+
2934
if (_respondsToSafeArea.HasValue)
3035
return _respondsToSafeArea.Value;
36+
3137
return (bool)(_respondsToSafeArea = RespondsToSelector(new Selector("safeAreaInsets")));
38+
3239
}
3340

3441
protected CGRect AdjustForSafeArea(CGRect bounds)
@@ -38,7 +45,7 @@ protected CGRect AdjustForSafeArea(CGRect bounds)
3845
KeyboardAutoManagerScroll.ShouldScrollAgain = true;
3946
}
4047

41-
if (View is not ISafeAreaView sav || sav.IgnoreSafeArea || !RespondsToSafeArea())
48+
if (!RespondsToSafeArea())
4249
{
4350
return bounds;
4451
}
@@ -91,6 +98,12 @@ Size CrossPlatformArrange(Rect bounds)
9198
return CrossPlatformLayout?.CrossPlatformArrange(bounds) ?? Size.Zero;
9299
}
93100

101+
// SizeThatFits does not take into account the constraints set on the view.
102+
// For example, if the user has set a width and height on this view, those constraints
103+
// will not be reflected in the value returned from this method. This method purely returns
104+
// a measure based on the size that is passed in.
105+
// The constraints are all applied by ViewHandlerExtensions.GetDesiredSizeFromHandler
106+
// after it calls this method.
94107
public override CGSize SizeThatFits(CGSize size)
95108
{
96109
if (_crossPlatformLayoutReference == null)
@@ -105,6 +118,25 @@ public override CGSize SizeThatFits(CGSize size)
105118

106119
CacheMeasureConstraints(widthConstraint, heightConstraint);
107120

121+
// If for some reason the upstream measure passes in a negative contraint
122+
// Lets just bypass this code
123+
if (RespondsToSafeArea() && widthConstraint >= 0 && heightConstraint >= 0)
124+
{
125+
// During the LayoutSubViews pass, we adjust the Bounds of this view for the safe area and then pass the adjusted result to CrossPlatformArrange.
126+
// The CrossPlatformMeasure call does not include the safe area, so we need to add it here to ensure the returned size is correct.
127+
//
128+
// For example, if this is a layout with an Entry of height 20, CrossPlatformMeasure will return a height of 20.
129+
// This means the bounds will be set to a height of 20, causing AdjustForSafeArea(Bounds) to return a negative bounds once it has
130+
// subtracted the safe area insets. Therefore, we need to add the safe area insets to the CrossPlatformMeasure result to ensure correct arrangement.
131+
var widthSafeAreaOffset = SafeAreaInsets.Left + SafeAreaInsets.Right;
132+
var heightSafeAreaOffset = SafeAreaInsets.Top + SafeAreaInsets.Bottom;
133+
134+
var width = double.Clamp(crossPlatformSize.Width + widthSafeAreaOffset, 0, widthConstraint);
135+
var height = double.Clamp(crossPlatformSize.Height + heightSafeAreaOffset, 0, heightConstraint);
136+
137+
return new CGSize(width, height);
138+
}
139+
108140
return crossPlatformSize.ToCGSize();
109141
}
110142

0 commit comments

Comments
 (0)