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:
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;
}
}
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;
}
}
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>