Skip to content

Commit ae262f2

Browse files
kubafloPureWeen
authored andcommitted
Fix SafeArea adjustments
Co-Authored-By: Shane Neuville <[email protected]>
1 parent ed53c15 commit ae262f2

File tree

5 files changed

+82
-1
lines changed

5 files changed

+82
-1
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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 x:Name="layout" Background="Purple" IgnoreSafeArea="False" VerticalOptions="Start" IsClippedToBounds="True" >
8+
<Entry x:Name="label" Background="Green" Text="Hello there"></Entry>
9+
</VerticalStackLayout>
10+
</ContentPage>
Lines changed: 11 additions & 0 deletions
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+
}
Lines changed: 26 additions & 0 deletions
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/MauiScrollView.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ public class MauiScrollView : UIScrollView, IUIViewLifeCycleEvents
99
{
1010
public MauiScrollView()
1111
{
12+
// By setting this to 'Never', we take full control of the content layout and manage the insets manually in our code, avoiding any automatic adjustments by the system.
13+
ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Never;
1214
}
1315

1416
// overriding this method so it does not automatically scroll large UITextFields

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ 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;
3136
return (bool)(_respondsToSafeArea = RespondsToSelector(new Selector("safeAreaInsets")));
@@ -38,7 +43,7 @@ protected CGRect AdjustForSafeArea(CGRect bounds)
3843
KeyboardAutoManagerScroll.ShouldScrollAgain = true;
3944
}
4045

41-
if (View is not ISafeAreaView sav || sav.IgnoreSafeArea || !RespondsToSafeArea())
46+
if (!RespondsToSafeArea())
4247
{
4348
return bounds;
4449
}
@@ -91,6 +96,12 @@ Size CrossPlatformArrange(Rect bounds)
9196
return CrossPlatformLayout?.CrossPlatformArrange(bounds) ?? Size.Zero;
9297
}
9398

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

106117
CacheMeasureConstraints(widthConstraint, heightConstraint);
107118

119+
// If for some reason the upstream measure passes in a negative contraint
120+
// Lets just bypass this code
121+
if (RespondsToSafeArea() && widthConstraint >= 0 && heightConstraint >= 0)
122+
{
123+
// During the LayoutSubViews pass, we adjust the Bounds of this view for the safe area and then pass the adjusted result to CrossPlatformArrange.
124+
// The CrossPlatformMeasure call does not include the safe area, so we need to add it here to ensure the returned size is correct.
125+
//
126+
// For example, if this is a layout with an Entry of height 20, CrossPlatformMeasure will return a height of 20.
127+
// This means the bounds will be set to a height of 20, causing AdjustForSafeArea(Bounds) to return a negative bounds once it has
128+
// subtracted the safe area insets. Therefore, we need to add the safe area insets to the CrossPlatformMeasure result to ensure correct arrangement.
129+
var widthSafeAreaOffset = SafeAreaInsets.Left + SafeAreaInsets.Right;
130+
var heightSafeAreaOffset = SafeAreaInsets.Top + SafeAreaInsets.Bottom;
131+
132+
CacheMeasureConstraints(widthConstraint + widthSafeAreaOffset, heightConstraint + heightSafeAreaOffset);
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)