Skip to content

Commit

Permalink
Ensure that UIScrollView ContentSize is set; Fixes #9209; also fixes #…
Browse files Browse the repository at this point in the history
  • Loading branch information
hartez authored and rmarinho committed Dec 12, 2022
1 parent d1ba6e0 commit 121d6fa
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 25 deletions.
12 changes: 2 additions & 10 deletions src/Controls/src/Core/HandlerImpl/ScrollView/ScrollView.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,9 @@ protected override Size ArrangeOverride(Rect bounds)

Size IContentView.CrossPlatformArrange(Rect bounds)
{
if ((this as IContentView).PresentedContent is IView presentedContent)
if (this is IScrollView scrollView)
{
var padding = Padding;

// Normally we'd just want the content to be arranged within the ContentView's Frame,
// but ScrollView content might be larger than the ScrollView itself (for obvious reasons)
// So in each dimension, we assume the larger of the two values.
bounds.Width = Math.Max(bounds.Width, presentedContent.DesiredSize.Width + padding.HorizontalThickness);
bounds.Height = Math.Max(bounds.Height, presentedContent.DesiredSize.Height + padding.VerticalThickness);

this.ArrangeContent(bounds);
return scrollView.ArrangeScrollViewContent(bounds);
}

return bounds.Size;
Expand Down
12 changes: 7 additions & 5 deletions src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,21 +150,21 @@ static void InsertContentView(UIScrollView platformScrollView, IScrollView scrol
Tag = ContentPanelTag
};

contentContainer.CrossPlatformArrange = ArrangeScrollViewContent(scrollView.CrossPlatformArrange, contentContainer);
contentContainer.CrossPlatformArrange = ArrangeScrollViewContent(scrollView.CrossPlatformArrange, contentContainer, platformScrollView);

platformScrollView.ClearSubviews();
contentContainer.AddSubview(platformContent);
platformScrollView.AddSubview(contentContainer);
}

static Func<Rect, Size> ArrangeScrollViewContent(Func<Rect, Size> internalArrange, ContentView container)
static Func<Rect, Size> ArrangeScrollViewContent(Func<Rect, Size> internalArrange, ContentView container, UIScrollView platformScrollView)
{
return (rect) =>
{
if (container.Superview is UIScrollView scrollView)
{
// 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 out side 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 = scrollView.Bounds;
Expand All @@ -176,7 +176,9 @@ static Func<Rect, Size> ArrangeScrollViewContent(Func<Rect, Size> internalArrang
container.Center = new CGPoint(container.Bounds.GetMidX(), container.Bounds.GetMidY());
}

return internalArrange(rect);
var contentSize = internalArrange(rect);
platformScrollView.ContentSize = contentSize;
return contentSize;
};
}

Expand Down Expand Up @@ -223,7 +225,7 @@ public override Size GetDesiredSize(double widthConstraint, double heightConstra
var virtualView = VirtualView;
var platformView = PlatformView;

if (platformView == null || virtualView == null || virtualView.PresentedContent == null)
if (platformView == null || virtualView == null)
{
return new Size(widthConstraint, heightConstraint);
}
Expand Down
22 changes: 22 additions & 0 deletions src/Core/src/Layouts/LayoutExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,27 @@ public static Size AdjustForFill(this Size size, Rect bounds, IView view)

return size;
}

internal static Size ArrangeScrollViewContent(this IScrollView scrollView, Rect bounds)
{
var presentedContent = scrollView.PresentedContent;

if (presentedContent == null)
{
return Size.Zero;
}

var padding = scrollView.Padding;

// Normally we'd just want the content to be arranged within the ContentView's Frame,
// but ScrollView content might be larger than the ScrollView itself (for obvious reasons)
// So in each dimension, we assume the larger of the two values.
bounds.Width = Math.Max(bounds.Width, presentedContent.DesiredSize.Width + padding.HorizontalThickness);
bounds.Height = Math.Max(bounds.Height, presentedContent.DesiredSize.Height + padding.VerticalThickness);

scrollView.ArrangeContent(bounds);

return bounds.Size;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,9 @@ protected void InitializeViewHandler(IElement element, IElementHandler handler,
var w = size.Width;
var h = size.Height;

if (double.IsPositiveInfinity(w))
w = view.Width;

if (double.IsPositiveInfinity(h))
h = view.Height;
// No measure method should be returning infinite values
Assert.False(double.IsPositiveInfinity(w));
Assert.False(double.IsPositiveInfinity(h));

#else
// Windows cannot measure without the view being loaded
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.DeviceTests.Stubs;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
Expand All @@ -18,7 +19,6 @@ public async Task ContentInitializesCorrectly()
{
bool result = await InvokeOnMainThreadAsync(() =>
{

var entry = new EntryStub() { Text = "In a ScrollView" };
var entryHandler = Activator.CreateInstance<EntryHandler>();
entryHandler.SetMauiContext(MauiContext);
Expand All @@ -34,8 +34,8 @@ public async Task ContentInitializesCorrectly()

foreach (var platformView in scrollViewHandler.PlatformView.Subviews)
{
// ScrollView on iOS uses an intermediate ContentView to handle conetent measurement/arrangement
if (platformView is ContentView contentView)
// ScrollView on iOS uses an intermediate ContentView to handle content measurement/arrangement
if (platformView is Microsoft.Maui.Platform.ContentView contentView)
{
foreach (var content in contentView.Subviews)
{
Expand All @@ -52,5 +52,38 @@ public async Task ContentInitializesCorrectly()

Assert.True(result, $"Expected (but did not find) a {nameof(MauiTextField)} in the Subviews array");
}

[Fact]
public async Task ScrollViewContentSizeSet()
{
var scrollView = new ScrollViewStub();

var scrollViewHandler = await InvokeOnMainThreadAsync(() =>
{
return CreateHandler(scrollView);
});

await InvokeOnMainThreadAsync(async () => {
await scrollViewHandler.PlatformView.AttachAndRun(() =>
{

var entry = new EntryStub() { Text = "In a ScrollView", Height = 10000 };
var entryHandler = Activator.CreateInstance<EntryHandler>();
entryHandler.SetMauiContext(MauiContext);
entryHandler.SetVirtualView(entry);
entry.Handler = entryHandler;

scrollView.Content = entry;

// Simulate a bunch of things that would happen if this were a real app
scrollViewHandler.UpdateValue(nameof(IScrollView.Content));
scrollViewHandler.PlatformArrange(new Rect(0, 0, 50, 50));
scrollViewHandler.PlatformView.SetNeedsLayout();
scrollViewHandler.PlatformView.LayoutIfNeeded();

Assert.Equal(10000, scrollViewHandler.PlatformView.ContentSize.Height);
});
});
}
}
}
13 changes: 12 additions & 1 deletion src/Core/tests/DeviceTests/Stubs/ScrollViewStub.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Layouts;

namespace Microsoft.Maui.DeviceTests.Stubs
{
Expand Down Expand Up @@ -26,12 +27,22 @@ public void ScrollFinished()

public Size CrossPlatformArrange(Rect bounds)
{
if (this is IScrollView scrollView)
{
return scrollView.ArrangeScrollViewContent(bounds);
}

return bounds.Size;
}

public Size CrossPlatformMeasure(double widthConstraint, double heightConstraint)
{
return new Size(widthConstraint, heightConstraint);
if (PresentedContent != null)
{
return PresentedContent.Measure(widthConstraint, heightConstraint);
}

return Size.Zero;
}
}
}
3 changes: 2 additions & 1 deletion src/Core/tests/DeviceTests/Stubs/StubBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ public Size Measure(double widthConstraint, double heightConstraint)
{
if (Handler != null)
{
return Handler.GetDesiredSize(widthConstraint, heightConstraint);
DesiredSize = Handler.GetDesiredSize(widthConstraint, heightConstraint);
return DesiredSize;
}

return new Size(widthConstraint, heightConstraint);
Expand Down

0 comments on commit 121d6fa

Please sign in to comment.