Search code examples
c#xamarin.formsmauiapp-shell

Switch App Shell Tab programmatically to current page on tab, not with GotoAsync - Xamarin Forms/Maui App Shell


How to switch tabs programmatically, as if the user had clicked the tabbar's tab button instead of using GoToAsync()

I have a Maui app (but the same in Xamarin Forms) with an App Shell <TabBar with 4 pages Each TabPage works independently, but I push new pages onto (Tab 1's) stack via

Shell.Current.Navigation.PushAsync(new ChildPage() );

(Further use case: The stack is dynamic, in that "ChildPage1" is not always in 1 place, and may be used several times in the stack, therefore I cannot use an absolute route path)

If I then use the navigation tab buttons, I can switch to a new tab and back, and it retains each Tab's stack (e.g. switching from Tab 2 to Tab 1 below would show "Child Page 2" [Tab 1] NewPage 1 Child Page 1 Child Page 2

[Tab 2] NewPage 2

[Tab 3] etc..

I need to be able to do this programmatically instead of clicking the tab button The only Navigation I have seen so far is

await Shell.Current.GoToAsync("//NewPage1");

which isn't what I want

Is there a way to do this? Is this even exposed, or do I have to manually track the Navigated() event for instance and GoToAsync the full remembered path?

sample app portions AppShell.xaml relevant portions

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="ShellNavTest.AppShell"
.. xmlns removed due to filters
    xmlns:local="clr-namespace:ShellNavTest"
    Shell.FlyoutBehavior="Disabled">
<!--
    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" /> -->
    <TabBar>
        <ShellContent Title="Page1" Route="NewPage1" ContentTemplate="{DataTemplate local:NewPage1}" />
        <ShellContent Title="Page2" Route="NewPage2" ContentTemplate="{DataTemplate local:NewPage2}" />
        <ShellContent Title="Page3" Route="NewPage3" ContentTemplate="{DataTemplate local:NewPage3}" />
        <ShellContent Title="Page4" Route="NewPage4" ContentTemplate="{DataTemplate local:NewPage4}" />
    </TabBar>
</Shell>

NewPage1.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
.. xmlns removed due to filters
             x:Class="ShellNavTest.NewPage1"
             Title="NewPage1"
             Shell.TabBarIsVisible="True" >
    <VerticalStackLayout>
        <Label 
            Text="Page 1"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
        <Button x:Name="btnCreateChild" Text="Create Child" Clicked="btnCreateChild_Clicked" Margin="20" />
    </VerticalStackLayout>
</ContentPage>

NewPage1.xaml.cs

namespace ShellNavTest;

public partial class NewPage1 : ContentPage
{
    public NewPage1()
    {
        InitializeComponent();
    }

    private void btnCreateChild_Clicked(object sender, EventArgs e)
    {
        Shell.Current.Navigation.PushAsync(new ChildPage() );
    }
}

ChildPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
.. xmlns removed due to filters
             x:Class="ShellNavTest.ChildPage"
             Title="ChildPage"
             Shell.TabBarIsVisible="True" >
    <VerticalStackLayout BackgroundColor="Cyan">
        <Label 
            Text="this is a child page on navigation stack but not in routes"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentPage>

NewPage2.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
.. xmlns removed due to filters
             x:Class="ShellNavTest.NewPage2"
             Title="NewPage2"
             Shell.TabBarIsVisible="True" >
    <VerticalStackLayout>
        <Label 
            Text="Page 2"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
        <Button x:Name="btnGotoPage1" Text="Goto Page 1" Clicked="btnGotoPage1_Clicked" Margin="20" />
    </VerticalStackLayout>
</ContentPage>

NewPage2.xaml.cs

namespace ShellNavTest;

public partial class NewPage2 : ContentPage
{
    public NewPage2()
    {
        InitializeComponent();
    }

    private async void btnGotoPage1_Clicked(object sender, EventArgs e)
    {
        await Shell.Current.GoToAsync("//NewPage1");
    }

}

Solution

  • I solved this by x:name -ing the TabBar and encompassing the ShellContent in named tabs, then setting the tabbar's CurrentItem specifically

    AppShell.xaml

        <TabBar x:Name="shelltabbar">
            <Tab x:Name="shelltab_0" Title="Page1">
            <ShellContent Title="Page1" Route="NewPage1" ContentTemplate="{DataTemplate local:NewPage1}" />
            </Tab>
            <Tab x:Name="shelltab_1" Title="Page2">
                <ShellContent Title="Page2" Route="NewPage2" ContentTemplate="{DataTemplate local:NewPage2}" />
            </Tab>
            <Tab x:Name="shelltab_2" Title="Page3">
                <ShellContent Title="Page3" Route="NewPage3" ContentTemplate="{DataTemplate local:NewPage3}" />
            </Tab>
            <Tab x:Name="shelltab_3" Title="Page4">
                <ShellContent Title="Page4" Route="NewPage4" ContentTemplate="{DataTemplate local:NewPage4}" />
            </Tab>
        </TabBar>
    

    AppShell.xaml.cs

    namespace ShellNavTest;
    
    public partial class AppShell : Shell
    {
        public AppShell()
        {
            InitializeComponent();
        }
        public void SwitchtoTab(int tabIndex)
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                switch (tabIndex)
                {
                    case 0: shelltabbar.CurrentItem = shelltab_0; break;
                    case 1: shelltabbar.CurrentItem = shelltab_1; break;
                    case 2: shelltabbar.CurrentItem = shelltab_2; break;
                    case 3: shelltabbar.CurrentItem = shelltab_3; break;
                };
            });
        }
    
    }
    

    ChildPage.xaml.cs

        private void btnSwitchToTab1_Clicked(object sender, EventArgs e)
        {
            ((AppShell)App.Current.MainPage).SwitchtoTab(0);
        }