diff --git a/src/Controls/tests/TestCases.HostApp/Elements/KeyboardScrollingEntriesPage.xaml b/src/Controls/tests/TestCases.HostApp/Elements/KeyboardScrollingEntriesPage.xaml
index 18e82d95659a..689a4212e775 100644
--- a/src/Controls/tests/TestCases.HostApp/Elements/KeyboardScrollingEntriesPage.xaml
+++ b/src/Controls/tests/TestCases.HostApp/Elements/KeyboardScrollingEntriesPage.xaml
@@ -4,28 +4,82 @@
AutomationId="KeyboardScrollingEntriesPage"
x:Class="Maui.Controls.Sample.KeyboardScrollingEntriesPage"
xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/tests/TestCases.HostApp/Elements/KeyboardScrollingGridPage.xaml b/src/Controls/tests/TestCases.HostApp/Elements/KeyboardScrollingGridPage.xaml
index c62e0538368e..996c86de8adc 100644
--- a/src/Controls/tests/TestCases.HostApp/Elements/KeyboardScrollingGridPage.xaml
+++ b/src/Controls/tests/TestCases.HostApp/Elements/KeyboardScrollingGridPage.xaml
@@ -3,15 +3,22 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
AutomationId="KeyboardScrollingGridPage"
x:Class="Maui.Controls.Sample.KeyboardScrollingGridPage">
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs b/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs
index 62cdbed39668..4ebe250f61c0 100644
--- a/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs
+++ b/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs
@@ -305,6 +305,14 @@ static string RemoveEverythingExceptForNumbersAndCommas(string input)
// all the fields are updated before calling AdjustPostition()
internal static async Task AdjustPositionDebounce()
{
+ // If View is inside a MauiView that implements ISafeAreaView2
+ // and has SafeAreaEdges.SoftInput set, do not perform auto-scrolling
+ // since SafeAreaEdges.SoftInput will handle the adjustments
+ if (View is not null && MauiView.IsSoftInputHandledByParent(View))
+ {
+ return;
+ }
+
if (IsKeyboardShowing)
{
// Universal 30ms delay for all input controls to ensure proper timing coordination
diff --git a/src/Core/src/Platform/iOS/MauiView.cs b/src/Core/src/Platform/iOS/MauiView.cs
index f14248c3c82e..b81437ceb57b 100644
--- a/src/Core/src/Platform/iOS/MauiView.cs
+++ b/src/Core/src/Platform/iOS/MauiView.cs
@@ -60,7 +60,7 @@ public abstract class MauiView : UIView, ICrossPlatformLayoutBacking, IVisualTre
// True if the view is an ISafeAreaView, does not ignore safe area, and is not inside a UIScrollView;
// otherwise, false. Null means not yet determined.
bool? _scrollViewDescendant;
-
+
// Keyboard tracking
CGRect _keyboardFrame = CGRect.Empty;
bool _isKeyboardShowing;
@@ -126,13 +126,13 @@ SafeAreaRegions GetSafeAreaRegionForEdge(int edge)
{
return safeAreaPage.GetSafeAreaRegionsForEdge(edge);
}
-
+
// Fallback to legacy ISafeAreaView behavior
if (View is ISafeAreaView sav)
{
return sav.IgnoreSafeArea ? SafeAreaRegions.None : SafeAreaRegions.Container;
}
-
+
return SafeAreaRegions.None;
}
@@ -141,7 +141,7 @@ static double GetSafeAreaForEdge(SafeAreaRegions safeAreaRegion, double original
// Edge-to-edge content - no safe area padding
if (safeAreaRegion == SafeAreaRegions.None)
return 0;
-
+
// All other regions respect safe area in some form
// This includes:
// - Default: Platform default behavior
@@ -167,7 +167,7 @@ protected CGRect AdjustForSafeArea(CGRect bounds)
{
KeyboardAutoManagerScroll.ShouldScrollAgain = true;
}
-
+
ValidateSafeArea();
return _safeArea.InsetRect(bounds);
}
@@ -215,7 +215,7 @@ void UnsubscribeFromKeyboardNotifications()
NSNotificationCenter.DefaultCenter.RemoveObserver(showObserver);
_keyboardWillShowObserver = null;
}
-
+
if (_keyboardWillHideObserver?.TryGetTarget(out var hideObserver) == true)
{
NSNotificationCenter.DefaultCenter.RemoveObserver(hideObserver);
@@ -267,7 +267,7 @@ void OnKeyboardWillHide(NSNotification notification)
}
return null;
}
-
+
SafeAreaPadding GetAdjustedSafeAreaInsets()
{
var baseSafeArea = SafeAreaInsets.ToSafeAreaInsets();
@@ -292,26 +292,33 @@ SafeAreaPadding GetAdjustedSafeAreaInsets()
{
// Get the keyboard frame and calculate its intersection with the current window
var window = this.Window;
-
+
if (window != null && !_keyboardFrame.IsEmpty)
{
var windowFrame = window.Frame;
var keyboardIntersection = CGRect.Intersect(_keyboardFrame, windowFrame);
-
+
// If keyboard is visible and intersects with window
if (!keyboardIntersection.IsEmpty)
{
- // Calculate keyboard height in the window's coordinate system
- var keyboardHeight = keyboardIntersection.Height;
-
+ var bottomEdgeRegion = safeAreaPage.GetSafeAreaRegionsForEdge(3); // 3 = bottom edge
+
// For SafeAreaRegions.SoftInput: Always pad so content doesn't go under the keyboard
-
// Bottom edge is most commonly affected by keyboard
- var bottomEdgeRegion = safeAreaPage.GetSafeAreaRegionsForEdge(3); // 3 = bottom edge
- if (SafeAreaEdges.IsSoftInput(bottomEdgeRegion))
+ if (SafeAreaEdges.IsSoftInput(bottomEdgeRegion) && !IsSoftInputHandledByParent(this))
{
// Use the larger of the current bottom safe area or the keyboard height
- var adjustedBottom = Math.Max(baseSafeArea.Bottom, keyboardHeight);
+ // Get the input control's bottom Y in window coordinates
+ var inputBottomY = 0.0;
+ if (Window is not null)
+ {
+ var viewFrameInWindow = this.Superview?.ConvertRectToView(this.Frame, Window) ?? this.Frame;
+ inputBottomY = viewFrameInWindow.Y + viewFrameInWindow.Height;
+ }
+ var keyboardTopY = _keyboardFrame.Y;
+ var overlap = inputBottomY > keyboardTopY ? (inputBottomY - keyboardTopY) : 0.0;
+
+ var adjustedBottom = (overlap > 0) ? overlap : baseSafeArea.Bottom;
baseSafeArea = new SafeAreaPadding(baseSafeArea.Left, baseSafeArea.Right, baseSafeArea.Top, adjustedBottom);
}
}
@@ -339,6 +346,21 @@ SafeAreaPadding GetAdjustedSafeAreaInsets()
return baseSafeArea;
}
+ ///
+ /// Checks if any parent view in the hierarchy is a MauiView that implements ISafeAreaView2
+ /// and has SafeAreaEdges.SoftInput set for the bottom edge. This is used to determine if
+ /// keyboard overlap/padding is already being handled by an ancestor, so the current view
+ /// should not apply additional adjustments.
+ /// Returns true if a parent is handling soft input, false otherwise.
+ ///
+ internal static bool IsSoftInputHandledByParent(UIView view)
+ {
+ return view.FindParent(x => x is MauiView mv
+ && mv.View is ISafeAreaView2 safeAreaView2
+ && SafeAreaEdges.IsSoftInput(safeAreaView2.GetSafeAreaRegionsForEdge(3))) is not null;
+ }
+
+
///
/// Checks if the current measure information is still valid for the given constraints.
/// This optimization avoids redundant measure operations when constraints haven't changed.