Skip to content

Commit

Permalink
Ensure zero-based arrangement rectangle
Browse files Browse the repository at this point in the history
Fixes #18513
  • Loading branch information
hartez committed Nov 8, 2023
1 parent 0d64894 commit 97be124
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,118 @@ 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);
});
});
}

internal class TestStackLayout : VerticalStackLayout
{
public Rect LastArrangeBounds { get; set; }

protected override Size ArrangeOverride(Rect bounds)
{
LastArrangeBounds = bounds;
return base.ArrangeOverride(bounds);
}
}

[Fact]
public async Task ContentChangeDoesNotResetScrollPosition()
{
var topLabel = new Label
{
WidthRequest = 100,
HeightRequest = 5000,
Text = "Hello",
BackgroundColor = Colors.LightBlue
};

var bottomLabel = new Label { Text = "Howdy" };

var layout = new TestStackLayout
{
topLabel,
bottomLabel
};

var scroll = new ScrollView
{
Content = layout,
HeightRequest = 400
};

var topLabelHandler = await CreateHandlerAsync<LabelHandler>(topLabel);
var bottomLabelHandler = await CreateHandlerAsync<LabelHandler>(bottomLabel);
var layoutHandler = await CreateHandlerAsync<LayoutHandler>(layout);
var scrollHandler = await CreateHandlerAsync<ScrollViewHandler>(scroll);

await AttachAndRun(scroll, async (handler) =>
{
var platformView = scrollHandler.PlatformView;
// Scroll down by 5000
scrollHandler.VirtualView.RequestScrollTo(0, 5000, true);
// Give it time to update
await Task.Delay(100);
// Verify that the content layout didn't pick up any incorrect offsets
// The arrangement for the actual _content_ should always start at Y=0 because
// of the ContentView shim. If we ever stop using the ContentView shim for
// the iOS ScrollView implementation, this test will likely become invalid.
Assert.Equal(0, layout.LastArrangeBounds.Top);
// Change the text of the bottom label; this _should_ have no effect on scrolling
bottomLabel.Text = "Changed";
await Task.Delay(100);
// The content should still be arranged at Y=0
Assert.Equal(0, layout.LastArrangeBounds.Top);
});
}
}
}
36 changes: 25 additions & 11 deletions src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,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 viewportWidth = viewportBounds.Width;
var viewportHeight = viewportBounds.Height;

var viewportSize = GetViewportSize(uiScrollView);
var viewportWidth = viewportSize.Width;
var viewportHeight = viewportSize.Height;

SetContentSizeForOrientation(uiScrollView, viewportWidth, viewportHeight, scrollView.Orientation, fullContentSize);
}

Expand Down Expand Up @@ -289,6 +291,11 @@ static void SetContentSizeForOrientation(UIScrollView uiScrollView, double viewp
uiScrollView.ContentSize = contentSize;
}

static CGSize GetViewportSize(UIScrollView platformScrollView)
{
return platformScrollView.AdjustedContentInset.InsetRect(platformScrollView.Bounds).Size;
}

Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double heightConstraint)
{
var scrollView = VirtualView;
Expand All @@ -301,17 +308,18 @@ Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double he
return Size.Zero;
}

var scrollViewBounds = platformScrollView.Bounds;
var viewportSize = GetViewportSize(platformScrollView);

var padding = scrollView.Padding;

if (widthConstraint == 0)
{
widthConstraint = scrollViewBounds.Width;
widthConstraint = viewportSize.Width;
}

if (heightConstraint == 0)
{
heightConstraint = scrollViewBounds.Height;
heightConstraint = viewportSize.Height;
}

// Account for the ScrollView Padding before measuring the content
Expand All @@ -334,9 +342,15 @@ Size ICrossPlatformLayout.CrossPlatformArrange(Rect 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 viewportHeight = viewportBounds.Height;
var viewportWidth = viewportBounds.Width;
var viewportSize = GetViewportSize(platformScrollView);

// Get a Rect for doing the CrossPlatformArrange of the Content
var viewportRect = new Rect(Point.Zero, viewportSize.ToSize());

var contentSize = crossPlatformLayout.CrossPlatformArrange(viewportRect);

var viewportHeight = viewportSize.Height;
var viewportWidth = viewportSize.Width;
SetContentSizeForOrientation(platformScrollView, viewportWidth, viewportHeight, scrollView.Orientation, contentSize);

var container = GetContentView(platformScrollView);
Expand All @@ -351,8 +365,8 @@ Size ICrossPlatformLayout.CrossPlatformArrange(Rect 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, viewportSize.Width),
Math.Max(containerBounds.Height, viewportSize.Height));

container.Center = new CGPoint(container.Bounds.GetMidX(), container.Bounds.GetMidY());
}
Expand Down

0 comments on commit 97be124

Please sign in to comment.