skip to Main Content

I have a VerticalStackLayout inside a ScrollView. Some items inside the VerticalStackLayout are shown/hidden dynamically at runtime. I noticed that, on iOS, the VerticalStackLayout is shifted down (it’s Y position changes) inside the ScrollView when it has been scrolled and the visibility status of one of it’s children changes after that. I think this should not happen at all. On Android it behaves as expected (the VerticalStackLayout keeps at top).

Steps to reproduce:

  • Create a new .NET MAUI App and change the XAML markup and code behind to the following:

XAML:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ScrollViewBug.MainPage">

    <ScrollView x:Name="MyScroll">
        <VerticalStackLayout
            x:Name="MyStack"
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Start"
            BackgroundColor="AliceBlue">

            <Image
                Source="dotnet_bot.png"
                SemanticProperties.Description="Cute dot net bot waving hi to you!"
                HeightRequest="200"
                HorizontalOptions="Center" />

            <Label
                Text="Hello, World!"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" />

            <Label
                Text="Welcome to .NET Multi-platform App UI"
                SemanticProperties.HeadingLevel="Level2"
                SemanticProperties.Description="Welcome to dot net Multi platform App U I"
                FontSize="18"
                HorizontalOptions="Center" />

            <Button
                x:Name="CounterBtn"
                Text="Click me"
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />

            <Grid x:Name="MyGrid" HeightRequest="600" BackgroundColor="Yellow" IsVisible="False"/>

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

Code behind:

using System.ComponentModel;

namespace ScrollViewBug;

public partial class MainPage : ContentPage
{
    int count = 0;

    public MainPage()
    {
        InitializeComponent();

        MyStack.PropertyChanged += StackPropertyChanged;
    }

    private void StackPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Y")
        {
            System.Diagnostics.Debug.WriteLine($"This Stack Y: {Math.Floor(MyStack.Y)}");
        }        
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;

        if (count == 1)
            CounterBtn.Text = $"Clicked {count} time";
        else
            CounterBtn.Text = $"Clicked {count} times";

        MyGrid.IsVisible = !MyGrid.IsVisible;

        SemanticScreenReader.Announce(CounterBtn.Text);
    }
}

Is there a way to make a custom handler that keeps the ScrollView sub-views at place (Y=0) when the sub-view ContentSize changes?

I’ve tried to intercept changes on the ScrollView ContentSize using a custom handler, and replace the subview frame (changing it’s Y position to zero), but it had no effect.

I’m using Visual Studio for Mac 17.6.6 (build 408) + .NET 8 RC2.

2

Answers


  1. I can reproduce your problem now. And the cause is the ScrollView doesn’t recalculate new size on iOS when the child’s visibility is changed to false.

    You can use the following code to resolve it:

    In the MauiProgram.cs:

        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });
    #if IOS
            ScrollViewHandler.Mapper.AppendToMapping("ContentSize", (handler, view) =>
            {
                handler.PlatformView.UpdateContentSize(handler.VirtualView.ContentSize);
                handler.PlatformArrange(handler.PlatformView.Frame.ToRectangle());
            });
    #endif
            return builder.Build();
        }
    
    Login or Signup to reply.
  2. I had the same issue and this didn’t fix it for me. I did a simple solution which is add TranslationY to the first child of the scrollview which is bound to the Y value of that same first child so that when Y changes (we don’t know why) the same value is added in negative to TranslationY. This in turn makes Y zero again fixing the issue.
    The code used for this is the follows:

    1. First add static resource for MathsExpressionConverter
    2. Then add binding for TranslationY so that it references Y property of the same view under ScrollView

    Here is the sample code for this:

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
                 x:Class="TestMaui.MainPage">
        <ContentPage.Resources>
            <ResourceDictionary>
                <toolkit:MathExpressionConverter x:Key="MathExpressionConverter" />
            </ResourceDictionary>
        </ContentPage.Resources>
        <ScrollView>
            <!--Any child like VerticalStackLayout-->
            <VerticalStackLayout TranslationY="{Binding Source={RelativeSource Self}, Path=Y, Converter={StaticResource MathExpressionConverter}, ConverterParameter='-1*x' }" >
               <!--Any content-->
            </VerticalStackLayout>
        </ScrollView>
    </ContentPage>
    

    I hope that helps someone out there. 😃

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search