diff --git a/src/Controls/tests/DeviceTests/Elements/ScrollView/ScrollViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/ScrollView/ScrollViewTests.iOS.cs index 8b441eb28761..afdd79b764e2 100644 --- a/src/Controls/tests/DeviceTests/Elements/ScrollView/ScrollViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/ScrollView/ScrollViewTests.iOS.cs @@ -7,6 +7,7 @@ using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; using Microsoft.Maui.Hosting; +using UIKit; using Xunit; namespace Microsoft.Maui.DeviceTests @@ -35,5 +36,52 @@ public async Task ScrollViewContentSizeSet() }); }); } + + [Fact] + public async Task ContentSizeExpandsToViewport() + { + EnsureHandlerCreated(builder => { builder.ConfigureMauiHandlers(handlers => { handlers.AddHandler(); }); }); + + var scrollView = new ScrollView(); + + var entry = new Entry() { Text = "In a ScrollView", HeightRequest = 10 }; + + + static CoreGraphics.CGSize getViewportSize(UIScrollView scrollView) + { + return scrollView.AdjustedContentInset.InsetRect(scrollView.Bounds).Size; + }; + + var scrollViewHandler = await InvokeOnMainThreadAsync(() => + { + return CreateHandlerAsync(scrollView); + }); + + await InvokeOnMainThreadAsync(async () => + { + await scrollViewHandler.PlatformView.AttachAndRun(async () => + { + var uiScrollView = scrollViewHandler.PlatformView; + + uiScrollView.ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Always; + var parent = uiScrollView.Superview; + uiScrollView.Bounds = parent.Bounds; + uiScrollView.Center = parent.Center; + + scrollView.Content = entry; + + parent.SetNeedsLayout(); + parent.LayoutIfNeeded(); + + await Task.Yield(); + + var contentSize = uiScrollView.ContentSize; + var viewportSize = getViewportSize(uiScrollView); + + Assert.Equal(viewportSize.Height, contentSize.Height); + Assert.Equal(viewportSize.Width, contentSize.Width); + }); + }); + } } } diff --git a/src/Controls/tests/UITests/snapshots/ios/Issue16094Test.png b/src/Controls/tests/UITests/snapshots/ios/Issue16094Test.png index d0ef6eb54aab..ec3ab47ffe9c 100644 Binary files a/src/Controls/tests/UITests/snapshots/ios/Issue16094Test.png and b/src/Controls/tests/UITests/snapshots/ios/Issue16094Test.png differ diff --git a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs index b86d4882bf44..60b6964c286a 100644 --- a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs +++ b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs @@ -1,13 +1,7 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing; -using System.Text; using CoreGraphics; using Microsoft.Maui.Graphics; using Microsoft.Maui.Layouts; -using Microsoft.Maui.Platform; -using ObjCRuntime; using UIKit; using Size = Microsoft.Maui.Graphics.Size; @@ -103,9 +97,11 @@ public static void MapOrientation(IScrollViewHandler handler, IScrollView scroll // without having to re-layout the ScrollView var fullContentSize = scrollView.PresentedContent?.DesiredSize ?? Size.Zero; - var viewportBounds = uiScrollView.Bounds; + + var viewportBounds = GetViewportBounds(uiScrollView); var viewportWidth = viewportBounds.Width; var viewportHeight = viewportBounds.Height; + SetContentSizeForOrientation(uiScrollView, viewportWidth, viewportHeight, scrollView.Orientation, fullContentSize); } @@ -127,7 +123,7 @@ public static void MapRequestScrollTo(IScrollViewHandler handler, IScrollView sc var availableScrollWidth = uiScrollView.ContentSize.Width - uiScrollView.Frame.Width; var minScrollHorizontal = Math.Min(request.HorizontalOffset, availableScrollWidth); var minScrollVertical = Math.Min(request.VerticalOffset, availableScrollHeight); - uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(minScrollHorizontal, minScrollVertical), !request.Instant); + uiScrollView.SetContentOffset(new CGPoint(minScrollHorizontal, minScrollVertical), !request.Instant); if (request.Instant) { @@ -185,16 +181,15 @@ static void InsertContentView(UIScrollView platformScrollView, IScrollView scrol return; } - var contentContainer = new ContentView() + var contentContainer = new ContentView { View = scrollView.PresentedContent, - Tag = ContentPanelTag + Tag = ContentPanelTag, + // This is where we normally would inject the cross-platform ScrollView's layout logic; instead, we're injecting the + // methods from this handler so it can make some adjustments for things like Padding before the default logic is invoked + CrossPlatformLayout = crossPlatformLayout }; - // This is where we normally would inject the cross-platform ScrollView's layout logic; instead, we're injecting the - // methods from this handler so it can make some adjustments for things like Padding before the default logic is invoked - contentContainer.CrossPlatformLayout = crossPlatformLayout; - platformScrollView.ClearSubviews(); contentContainer.AddSubview(platformContent); platformScrollView.AddSubview(contentContainer); @@ -289,6 +284,11 @@ static void SetContentSizeForOrientation(UIScrollView uiScrollView, double viewp uiScrollView.ContentSize = contentSize; } + static CGRect GetViewportBounds(UIScrollView platformScrollView) + { + return platformScrollView.AdjustedContentInset.InsetRect(platformScrollView.Bounds); + } + Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double heightConstraint) { var scrollView = VirtualView; @@ -301,17 +301,18 @@ Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double he return Size.Zero; } - var scrollViewBounds = platformScrollView.Bounds; + var viewPortBounds = GetViewportBounds(platformScrollView); + var padding = scrollView.Padding; if (widthConstraint == 0) { - widthConstraint = scrollViewBounds.Width; + widthConstraint = viewPortBounds.Width; } if (heightConstraint == 0) { - heightConstraint = scrollViewBounds.Height; + heightConstraint = viewPortBounds.Height; } // Account for the ScrollView Padding before measuring the content @@ -330,29 +331,28 @@ Size ICrossPlatformLayout.CrossPlatformArrange(Rect bounds) var crossPlatformLayout = scrollView as ICrossPlatformLayout; var platformScrollView = PlatformView; - var contentSize = crossPlatformLayout.CrossPlatformArrange(bounds); - // The UIScrollView's bounds are available, so we can use them to make sure the ContentSize makes sense // for the ScrollView orientation - var viewportBounds = platformScrollView.Bounds; + var viewportBounds = GetViewportBounds(platformScrollView); + + var contentSize = crossPlatformLayout.CrossPlatformArrange(viewportBounds.ToRectangle()); + var viewportHeight = viewportBounds.Height; var viewportWidth = viewportBounds.Width; SetContentSizeForOrientation(platformScrollView, viewportWidth, viewportHeight, scrollView.Orientation, contentSize); var container = GetContentView(platformScrollView); - if (container?.Superview is UIScrollView uiScrollView) + if (container != null) { // Ensure the container is at least the size of the UIScrollView itself, so that the // cross-platform layout logic makes sense and the contents don't arrange outside the // container. (Everything will look correct if they do, but hit testing won't work properly.) - - var scrollViewBounds = uiScrollView.Bounds; var containerBounds = contentSize; container.Bounds = new CGRect(0, 0, - Math.Max(containerBounds.Width, scrollViewBounds.Width), - Math.Max(containerBounds.Height, scrollViewBounds.Height)); + Math.Max(containerBounds.Width, viewportBounds.Width), + Math.Max(containerBounds.Height, viewportBounds.Height)); container.Center = new CGPoint(container.Bounds.GetMidX(), container.Bounds.GetMidY()); }