jkh
jkh

Reputation: 3678

Correct place to call ScrollToAsync when ItemsSource changes in view model using Xamarin.Forms

On my ContentPage, I use an ItemsControl (https://github.com/xamarinhq/xamu-infrastructure/blob/master/src/XamU.Infrastructure/Controls/ItemsControl.cs) to display a horizontal StackLayout of templated items bound to an ItemsSource, inside a ScrollView.

<ScrollView Orientation="Horizontal" x:Name="MyScrollView"> <userControls:ItemsControl ItemsSource="{Binding MyItems}" x:Name="MyItemsControl"> <userControls:ItemsControl.ItemTemplate> <DataTemplate> ... </DataTemplate> </userControls:ItemsControl.ItemTemplate> </userControls:ItemsControl> </ScrollView>

When I navigate to my page, my view model sets MyItems to an ObservableCollection, and the ItemsControl may exceed the screen width. When this happens, I need to scroll to the end of the ScrollView so the last elements in MyItems are visible.

I have tried overriding LayoutChildren in both the page and the ItemsControl, and while it sometimes works, it also is inconsistent and sometimes stops the scroll view part of the way through the items.

protected override async void LayoutChildren(double x, double y, double width, double height)
{
  base.LayoutChildren(x, y, width, height);
  if (_previousWidth != MyItemsControl.Width)
  {
    _previousWidth = MyItemsControl.Width;
    if (MyItemsControl.Width > screenWidth)
    {
      //await Task.Yield();
      await BreadcrumbScrollView.ScrollToAsync(_previousWidth-screenWidth, 0, false);
    }
  }
}

Since LayoutChildren gets called multiple times (and varies by platform), is there any way to know when the layout is complete so that I can call ScrollToAsync once?

Upvotes: 2

Views: 727

Answers (1)

Sten Petrov
Sten Petrov

Reputation: 11040

You can try delaying the scroll by a tiny bit to avoid conflicts with animations such as keyboard appearing or page popping in, it's worked for me in the past.

You can also put a Debug.WriteLine before the scroll to check if it's not being called more than once for some reason, if that's the case - add a flag to only run it once.

Here's how to delay the scroll, admittedly a hack:

Device.BeginInvokeOnMainThread(async () =>
{
    if (MyItemsControl.Width > screenWidth)
    { 
        await Task.Delay(25);
        await BreadcrumbScrollView.ScrollToAsync(_previousWidth-screenWidth, 0, false);   
    }
});

Upvotes: 1

Related Questions