Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,71 @@ await scrollViewHandler.PlatformView.AttachAndRun(async () =>
});
});
}

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);
});
}
}
}
46 changes: 25 additions & 21 deletions src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ public static void MapOrientation(IScrollViewHandler handler, IScrollView scroll

var fullContentSize = scrollView.PresentedContent?.DesiredSize ?? Size.Zero;

var viewportBounds = GetViewportBounds(uiScrollView);
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 All @@ -123,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 CGPoint(minScrollHorizontal, minScrollVertical), !request.Instant);
uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(minScrollHorizontal, minScrollVertical), !request.Instant);

if (request.Instant)
{
Expand Down Expand Up @@ -181,15 +181,16 @@ static void InsertContentView(UIScrollView platformScrollView, IScrollView scrol
return;
}

var contentContainer = new ContentView
var contentContainer = new ContentView()
{
View = scrollView.PresentedContent,
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
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
contentContainer.CrossPlatformLayout = crossPlatformLayout;

platformScrollView.ClearSubviews();
contentContainer.AddSubview(platformContent);
platformScrollView.AddSubview(contentContainer);
Expand Down Expand Up @@ -284,9 +285,9 @@ static void SetContentSizeForOrientation(UIScrollView uiScrollView, double viewp
uiScrollView.ContentSize = contentSize;
}

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

Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double heightConstraint)
Expand All @@ -301,18 +302,18 @@ Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double he
return Size.Zero;
}

var viewPortBounds = GetViewportBounds(platformScrollView);
var viewportSize = GetViewportSize(platformScrollView);

var padding = scrollView.Padding;

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

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

// Account for the ScrollView Padding before measuring the content
Expand All @@ -333,26 +334,29 @@ 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 = GetViewportBounds(platformScrollView);
var viewportSize = GetViewportSize(platformScrollView);

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

var contentSize = crossPlatformLayout.CrossPlatformArrange(viewportBounds.ToRectangle());
var contentSize = crossPlatformLayout.CrossPlatformArrange(viewportRect);

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

var container = GetContentView(platformScrollView);

if (container != null)
if (container?.Superview is UIScrollView uiScrollView)
{
// 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 containerBounds = contentSize;

container.Bounds = new CGRect(0, 0,
Math.Max(containerBounds.Width, viewportBounds.Width),
Math.Max(containerBounds.Height, viewportBounds.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