Skip to content

Commit 0cb5d80

Browse files
authored
Ensure that viewport calculations for iOS ScrollView account for content insets (#18277)
* Ensure that viewport calculations for iOS ScrollView account for content insets Fixes #17224 * Update baseline image
1 parent 01d6b39 commit 0cb5d80

File tree

3 files changed

+73
-25
lines changed

3 files changed

+73
-25
lines changed

src/Controls/tests/DeviceTests/Elements/ScrollView/ScrollViewTests.iOS.cs

+48
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Maui.Graphics;
88
using Microsoft.Maui.Handlers;
99
using Microsoft.Maui.Hosting;
10+
using UIKit;
1011
using Xunit;
1112

1213
namespace Microsoft.Maui.DeviceTests
@@ -35,5 +36,52 @@ await scrollViewHandler.PlatformView.AttachAndRun(() =>
3536
});
3637
});
3738
}
39+
40+
[Fact]
41+
public async Task ContentSizeExpandsToViewport()
42+
{
43+
EnsureHandlerCreated(builder => { builder.ConfigureMauiHandlers(handlers => { handlers.AddHandler<Entry, EntryHandler>(); }); });
44+
45+
var scrollView = new ScrollView();
46+
47+
var entry = new Entry() { Text = "In a ScrollView", HeightRequest = 10 };
48+
49+
50+
static CoreGraphics.CGSize getViewportSize(UIScrollView scrollView)
51+
{
52+
return scrollView.AdjustedContentInset.InsetRect(scrollView.Bounds).Size;
53+
};
54+
55+
var scrollViewHandler = await InvokeOnMainThreadAsync(() =>
56+
{
57+
return CreateHandlerAsync<ScrollViewHandler>(scrollView);
58+
});
59+
60+
await InvokeOnMainThreadAsync(async () =>
61+
{
62+
await scrollViewHandler.PlatformView.AttachAndRun(async () =>
63+
{
64+
var uiScrollView = scrollViewHandler.PlatformView;
65+
66+
uiScrollView.ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Always;
67+
var parent = uiScrollView.Superview;
68+
uiScrollView.Bounds = parent.Bounds;
69+
uiScrollView.Center = parent.Center;
70+
71+
scrollView.Content = entry;
72+
73+
parent.SetNeedsLayout();
74+
parent.LayoutIfNeeded();
75+
76+
await Task.Yield();
77+
78+
var contentSize = uiScrollView.ContentSize;
79+
var viewportSize = getViewportSize(uiScrollView);
80+
81+
Assert.Equal(viewportSize.Height, contentSize.Height);
82+
Assert.Equal(viewportSize.Width, contentSize.Width);
83+
});
84+
});
85+
}
3886
}
3987
}
Loading

src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs

+25-25
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.ComponentModel;
4-
using System.Drawing;
5-
using System.Text;
62
using CoreGraphics;
73
using Microsoft.Maui.Graphics;
84
using Microsoft.Maui.Layouts;
9-
using Microsoft.Maui.Platform;
10-
using ObjCRuntime;
115
using UIKit;
126
using Size = Microsoft.Maui.Graphics.Size;
137

@@ -103,9 +97,11 @@ public static void MapOrientation(IScrollViewHandler handler, IScrollView scroll
10397
// without having to re-layout the ScrollView
10498

10599
var fullContentSize = scrollView.PresentedContent?.DesiredSize ?? Size.Zero;
106-
var viewportBounds = uiScrollView.Bounds;
100+
101+
var viewportBounds = GetViewportBounds(uiScrollView);
107102
var viewportWidth = viewportBounds.Width;
108103
var viewportHeight = viewportBounds.Height;
104+
109105
SetContentSizeForOrientation(uiScrollView, viewportWidth, viewportHeight, scrollView.Orientation, fullContentSize);
110106
}
111107

@@ -127,7 +123,7 @@ public static void MapRequestScrollTo(IScrollViewHandler handler, IScrollView sc
127123
var availableScrollWidth = uiScrollView.ContentSize.Width - uiScrollView.Frame.Width;
128124
var minScrollHorizontal = Math.Min(request.HorizontalOffset, availableScrollWidth);
129125
var minScrollVertical = Math.Min(request.VerticalOffset, availableScrollHeight);
130-
uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(minScrollHorizontal, minScrollVertical), !request.Instant);
126+
uiScrollView.SetContentOffset(new CGPoint(minScrollHorizontal, minScrollVertical), !request.Instant);
131127

132128
if (request.Instant)
133129
{
@@ -185,16 +181,15 @@ static void InsertContentView(UIScrollView platformScrollView, IScrollView scrol
185181
return;
186182
}
187183

188-
var contentContainer = new ContentView()
184+
var contentContainer = new ContentView
189185
{
190186
View = scrollView.PresentedContent,
191-
Tag = ContentPanelTag
187+
Tag = ContentPanelTag,
188+
// This is where we normally would inject the cross-platform ScrollView's layout logic; instead, we're injecting the
189+
// methods from this handler so it can make some adjustments for things like Padding before the default logic is invoked
190+
CrossPlatformLayout = crossPlatformLayout
192191
};
193192

194-
// This is where we normally would inject the cross-platform ScrollView's layout logic; instead, we're injecting the
195-
// methods from this handler so it can make some adjustments for things like Padding before the default logic is invoked
196-
contentContainer.CrossPlatformLayout = crossPlatformLayout;
197-
198193
platformScrollView.ClearSubviews();
199194
contentContainer.AddSubview(platformContent);
200195
platformScrollView.AddSubview(contentContainer);
@@ -289,6 +284,11 @@ static void SetContentSizeForOrientation(UIScrollView uiScrollView, double viewp
289284
uiScrollView.ContentSize = contentSize;
290285
}
291286

287+
static CGRect GetViewportBounds(UIScrollView platformScrollView)
288+
{
289+
return platformScrollView.AdjustedContentInset.InsetRect(platformScrollView.Bounds);
290+
}
291+
292292
Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double heightConstraint)
293293
{
294294
var scrollView = VirtualView;
@@ -301,17 +301,18 @@ Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double he
301301
return Size.Zero;
302302
}
303303

304-
var scrollViewBounds = platformScrollView.Bounds;
304+
var viewPortBounds = GetViewportBounds(platformScrollView);
305+
305306
var padding = scrollView.Padding;
306307

307308
if (widthConstraint == 0)
308309
{
309-
widthConstraint = scrollViewBounds.Width;
310+
widthConstraint = viewPortBounds.Width;
310311
}
311312

312313
if (heightConstraint == 0)
313314
{
314-
heightConstraint = scrollViewBounds.Height;
315+
heightConstraint = viewPortBounds.Height;
315316
}
316317

317318
// Account for the ScrollView Padding before measuring the content
@@ -330,29 +331,28 @@ Size ICrossPlatformLayout.CrossPlatformArrange(Rect bounds)
330331
var crossPlatformLayout = scrollView as ICrossPlatformLayout;
331332
var platformScrollView = PlatformView;
332333

333-
var contentSize = crossPlatformLayout.CrossPlatformArrange(bounds);
334-
335334
// The UIScrollView's bounds are available, so we can use them to make sure the ContentSize makes sense
336335
// for the ScrollView orientation
337-
var viewportBounds = platformScrollView.Bounds;
336+
var viewportBounds = GetViewportBounds(platformScrollView);
337+
338+
var contentSize = crossPlatformLayout.CrossPlatformArrange(viewportBounds.ToRectangle());
339+
338340
var viewportHeight = viewportBounds.Height;
339341
var viewportWidth = viewportBounds.Width;
340342
SetContentSizeForOrientation(platformScrollView, viewportWidth, viewportHeight, scrollView.Orientation, contentSize);
341343

342344
var container = GetContentView(platformScrollView);
343345

344-
if (container?.Superview is UIScrollView uiScrollView)
346+
if (container != null)
345347
{
346348
// Ensure the container is at least the size of the UIScrollView itself, so that the
347349
// cross-platform layout logic makes sense and the contents don't arrange outside the
348350
// container. (Everything will look correct if they do, but hit testing won't work properly.)
349-
350-
var scrollViewBounds = uiScrollView.Bounds;
351351
var containerBounds = contentSize;
352352

353353
container.Bounds = new CGRect(0, 0,
354-
Math.Max(containerBounds.Width, scrollViewBounds.Width),
355-
Math.Max(containerBounds.Height, scrollViewBounds.Height));
354+
Math.Max(containerBounds.Width, viewportBounds.Width),
355+
Math.Max(containerBounds.Height, viewportBounds.Height));
356356

357357
container.Center = new CGPoint(container.Bounds.GetMidX(), container.Bounds.GetMidY());
358358
}

0 commit comments

Comments
 (0)