Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/8.0.1xx-rc2.2] Ensure that viewport calculations for iOS ScrollView account for content insets #18321

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -7,6 +7,7 @@
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using UIKit;
using Xunit;

namespace Microsoft.Maui.DeviceTests
Expand Down Expand Up @@ -35,5 +36,52 @@ public async Task ScrollViewContentSizeSet()
});
});
}

[Fact]
public async Task ContentSizeExpandsToViewport()
{
EnsureHandlerCreated(builder => { builder.ConfigureMauiHandlers(handlers => { handlers.AddHandler<Entry, EntryHandler>(); }); });

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<ScrollViewHandler>(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);
});
});
}
}
}
Binary file modified src/Controls/tests/UITests/snapshots/ios/Issue16094Test.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 25 additions & 25 deletions 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;

Expand Down Expand Up @@ -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);
}

Expand All @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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());
}
Expand Down