Skip to content

Commit

Permalink
Ensure that viewport calculations for iOS ScrollView account for cont…
Browse files Browse the repository at this point in the history
…ent insets (dotnet#18277)

* Ensure that viewport calculations for iOS ScrollView account for content insets
Fixes dotnet#17224

* Update baseline image
  • Loading branch information
hartez authored Oct 24, 2023
1 parent 01d6b39 commit 0cb5d80
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 25 deletions.
Original file line number Diff line number Diff line change
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 @@ await scrollViewHandler.PlatformView.AttachAndRun(() =>
});
});
}

[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
Loading
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
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 0cb5d80

Please sign in to comment.