Search code examples
c#.netmaui

.NET MAUI - Back button not sending user to top of previous page


I have a button that sends the user back to the page they came from.

However, after clicking on the button the user is sent back to the scrollable view they came from, and not to the top of the screen. I have a feeling this is due to the ScrollView.

Here is the steps by step process:

  1. user scrolls half way through the bottom of the screen on page A;
  2. user clicks a button to get to page B;
  3. from page B, user clicks button to return to page A;
  4. user is redirected half way through the bottom of the screen on page A;

How can I override this and send the user to the top of page A from clicking on button on Page B?

PageBVM

[RelayCommand]
private async void OnNavigateBackToPageAView()
{
    try
    {
        await Shell.Current.GoToAsync("///pageA",
            new Dictionary<string, object>
            {
                {"pageA", pageA }
            });
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"Error navigation back to page A view: {ex.Message}");
        if (ex.InnerException != null)
        {
            Debug.WriteLine($"Inner Exception: {ex.InnerException.Message}");
        }

    }
}

PageA.xaml

<Grid>
    <!-- Scrollable Content -->
    <ScrollView>
        <VerticalStackLayout>
        </VerticalStackLayout>
   </ScrollView>
</Grid>

PageA.xaml.cs

public partial class PageAView: ContentPage
{
    {
        InitializeComponent();
        BindingContext = vm;
    }
}

Solution

  • Doing this is a bit tricky in terms of timing, because the layout needs to be fully calculated before you can do something like scrollView.ScrollToAsync(0, 0, false). But by triggering on the NavigatedTo event and then scheduling the action at the end of the existing message queue, you should see reliable timing. A fallback method, involving a "magic delay" of a few milliseconds, is also shown but we try and avoid this kind of thing when we can.

    I'll post in the comments the full code I used to test this answer and you can make sure it works on your end.

    public partial class PageAView : ContentPage
    {
        public PageAView() => InitializeComponent();
    
        private async void OnFwdNavClicked(object sender, EventArgs e)
        {
            await Shell.Current.GoToAsync("///PageB");
        }
    
        protected async override void OnNavigatedTo(NavigatedToEventArgs args)
        {
            base.OnNavigatedTo(args);
    #if true
            Dispatcher.Dispatch(async () =>
            {
                await scrollView.ScrollToAsync(0, 0, false);
            });
    #else
            // AVOID "MAGIC DELAYS" WHEN YOU CAN
            await Task.Delay(1);
            await scrollView.ScrollToAsync(0, 0, false);
    #endif
        }
    }
    

    A couple of suggestions.

    • First, having a UI dependency in the ViewModel is not strictly speaking an MVVM approach. Shell.Current.GoToAsync(...) is something that actually belongs in the code-behind, not the VM.

    • Second, since a platform like Android is going to have a system "Back Navigation" button, you should get it involved and make it do what you want it to do.

    public partial class PageBView : ContentPage
    {
        public PageBView() => InitializeComponent();
    
        // This refers to "our" xaml-defined back button in the page header.
        private async void OnBackButtonClicked(object sender, EventArgs e)
        {
            await Shell.Current.GoToAsync("///PageA");
        }
        // This refers to the SYSTEM back button
        protected override bool OnBackButtonPressed()
        {
            _ = Shell.Current.GoToAsync("///PageA");
            return true;
        }
    }
    

    system back nav


    XAML for Minimal Example

    <ContentPage
        xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="back_nav_with_scroll_top.PageAView">
        <ScrollView
            x:Name="scrollView">
            <Grid
                Padding="30,0"
                RowDefinitions="200,200,*"
                HeightRequest="1500">
                <Image
                    Source="dotnet_bot.png"
                    HeightRequest="185"
                    Aspect="AspectFit"
                    SemanticProperties.Description="dot net bot in a race car number eight" />
                <Label 
                    Text="Scroll down..."
                    VerticalOptions="Center"
                    Grid.Row="1" />
                <Button
                    x:Name="buttonFwdNav"
                    Grid.Row="2"
                    Text="Page B" 
                    Clicked="OnFwdNavClicked"
                    HorizontalOptions="Fill"
                    VerticalOptions="Center"/>
            </Grid>
        </ScrollView>
    </ContentPage>