1
votes

I am currently developing app using Xamarin.Forms. I am facing some serious issue in iOS. My root page is TabbedPage and I want to make my tabs visible for entire applications. Hence I am setting MainPage as below

App.cs

MainPage = new NavigationPage(new MyTabbedPage());

MyTabbedPage.cs

Children.Add(new NavigationPage(new FirstTabbedPage()));
Children.Add(new NavigationPage(new SecondTabbedPage()));
Children.Add(new NavigationPage(new ThirdTabbedPage()));

Both FirstTabbedPage & SecondTabbedPage shows DataGrid using DevExpress.Mobile. On tap of any row from the Grid I am Navigating to another ContentPage say MyContentPage within the Root Tab.

SecondTabbePage.cs

private async void Grid_RowTap(object sender, RowTapEventArgs e)
        {
        //Some code logic, to get data from server
            await Navigation.PushAsync(new MyContentPage());
        }

For Example I am navigating to MyContentPage from SecondTabbedPage. From ContentPage I am Navigating to FirstTabbedPage. Now if I click SecondTabbedPage, MyContentPage will be shown, but I don't want this behaviour hence I am removing the page from NavigationStack in OnDisappearing method of MyContentPage as below:

MyContentPage.cs

protected override void OnDisappearing()
        {
            base.OnDisappearing();

            //Clear Navigation Stack, clicking on tab page should always 
            //go to corresponding page
            try
            {
                var existingPages = Navigation.NavigationStack.ToList();
                foreach (var page in existingPages)
                {                        
                    //existingPages count should be greater than 1, so that this will never be root page. Otherwise removing root page will throw exception
                    if (string.Compare(page.GetType().Name, "MyContentPage", StringComparison.OrdinalIgnoreCase) == 0 && existingPages.Count > 1)
                    {
                        Navigation.RemovePage(page);
                    }

                }
         //Just to check whether page was removed or not, but still was able to see MyContentPage even after removing it from Navigation Stack
             var existingPages = Navigation.NavigationStack.ToList();
            }
            catch(Exception ex)
            {

            }
        }

Now the issues are:

  1. Even after calling RemovePage(MyContentPage) I am able to see that page in Debug mode.
  2. Due to this behaviour await Navigation.PushAsync(new MyContentPage()); is not navigating to MyContentPage second time even-though it executes code without any exception.

The same code is working fine in Android as Android life cycle is different which I was able to see in Debug mode

Few Things I Tried Are:

  1. Removed NavigationPage from MainPage (TabbedPage) as I read that TabbedPage inside NavigationPage is not good design for iOS.
  2. Removed NavigationPage on Child Items but tabbed icons were not shown after navigating to MyContentPage
  3. Tried removing MyContentPage from NavigationStack in OnAppearing event of FirstTabbedPage.
2

2 Answers

1
votes

As you said, due to the Android life cycle is different from iOS, I would recommend you to achieve the requirement by using custom renderer in iOS.

You should create a custom renderer of your MyTabbedPage, and then in ViewControllerSelected event, remove your ContentPage from NavigationStack.

[assembly: ExportRenderer(typeof(MainPage), typeof(myTabbarRenderer))]
namespace TabbedPageWithNavigationPage.iOS
{
    class myTabbarRenderer : TabbedRenderer
    {
        private MainPage _page;
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
            {
                _page = (MainPage)e.NewElement;
            }
            else
            {
                _page = (MainPage)e.OldElement;
            }

            try
            {
                var tabbarController = (UITabBarController)this.ViewController;
                if (null != tabbarController)
                {
                    tabbarController.ViewControllerSelected += OnTabbarControllerItemSelected;
                }
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
            }
        }

        private async void OnTabbarControllerItemSelected(object sender, UITabBarSelectionEventArgs eventArgs)
        {
            if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
            {
                await _page.CurrentPage.Navigation.PopToRootAsync();
            }

        }
    }
}

And for Android, add Device.RuntimePlatform == Device.Android to make sure the code only works on Android platform:

protected override void OnDisappearing()
{
    base.OnDisappearing();

    //Clear Navigation Stack, clicking on tab page should always 
    //go to corresponding page

    if (Device.RuntimePlatform == Device.Android)
    {
        try
        {
            var existingPages = Navigation.NavigationStack.ToList();
            foreach (var page in existingPages)
            {
                //existingPages count should be greater than 1, so that this will never be root page. Otherwise removing root page will throw exception
                if (string.Compare(page.GetType().Name, "UpcomingAppointmentsPage", StringComparison.OrdinalIgnoreCase) == 0 && existingPages.Count > 1)
                {
                    Navigation.RemovePage(page);
                }

            }                 
        }
        catch (Exception ex)
        {

        }
    }           
}

I write a demo here and you can check it. Let me know if it works.

0
votes

I tried @Jackhua solution. It works perfect as well. But I used following way to fix the above problem as suggested in Xamarin Forum.

Reference: https://forums.xamarin.com/discussion/comment/375638#Comment_375638

MyTabbedPage.cs

    Page page1 = null;
    Page page2 = null;
    Page page3 = null;
    public MainPage()
    {
        InitializeComponent();

        page1 = new NavigationPage(new FirstTabbedPage());
        page1.Title = "Page1";

        page2 = new NavigationPage(new SecondTabbedPage());
        page2.Title = "Page2";

        page3 = new NavigationPage(new ThirdTabbedPage());
        page3.Title = "Page3";

        Children.Add(page1);
        Children.Add(page2);
        Children.Add(page3);
    }

    protected override void OnCurrentPageChanged()
    {
        base.OnCurrentPageChanged();

        int index = Children.IndexOf(CurrentPage);


        if (index == 0)
        {
            if (Children.Count > 1)
            {
                page2 = Children[1] as NavigationPage;
                page2.Navigation.PopToRootAsync();

                page3 = Children[2] as NavigationPage;
                page3.Navigation.PopToRootAsync();
            }
        }

        else if (index == 1)
        {
            if (Children.Count > 1)
            {
                page1 = Children[0] as NavigationPage;
                page1.Navigation.PopToRootAsync();

                page3 = Children[2] as NavigationPage;
                page3.Navigation.PopToRootAsync();
            }
        }

        else if (index == 2)
        {
            if (Children.Count > 1)
            {
                page1 = Children[0] as NavigationPage;
                page1.Navigation.PopToRootAsync();

                page2 = Children[1] as NavigationPage;
                page2.Navigation.PopToRootAsync();
            }
        }
    }

Above solution doesn't require Custom Renderer and works for both Android & iOS.

Also code block under MyContentPage.cs is not required. i.e remove iterating of existingPages under OnDisappearing method