Show "Back to Menu" Button in iOS NavigationBar with Xamarin.Forms
Asked Answered
A

3

8

I'm trying to build a cross-platform app using C# and Xamarin.Forms. It contains a slide-out menu implemented in form of a MasterDetailPage. While on Android there is a button with the app icon in the top left corner, which toggles the slide-out page, there is no such navigation bar item on iOS.

I broke it down to the following minimum example derived from the Xamarin solution template "Blank App (Xamarin.Forms Shared)" and replacing the implementation of the App-class:

public class App
{
    static MasterDetailPage MDPage;

    public static Page GetMainPage()
    {
        return new NavigationPage(
            MDPage = new MasterDetailPage {
                Master = new ContentPage {
                    Title = "Master",
                    Content = new StackLayout {
                        Children = { Link("A"), Link("B"), Link("C") }
                    },
                },
                Detail = new ContentPage { Content = new Label { Text = "A" } },
            });
    }

    static Button Link(string name)
    {
        var button = new Button { Text = name };
        button.Clicked += delegate {
            MDPage.Detail = new ContentPage { Content = new Label { Text = name } };
            MDPage.IsPresented = false;
        };
        return button;
    }
}

The solution as well as resulting screenshots can be found at GitHub.

My idea was to add such a "menu" or "back" button in the iOS-specific code modifying the window.RootViewController.NavigationController.NavigationBar within the AppDelegate class. But window.RootViewController.NavigationController is null.

Replacing the return type of GetMainPage() by NavigationPage instead of Page does not help.

I could add toolbar items via MDPage.ToolbarItems.Add(...), but they appear in the top right corner.

Anthropophagite answered 11/7, 2014 at 14:28 Comment(0)
A
1

I finally found a solution. The code basically needs two minor corrections:

  1. Wrap all DetailPages in a NavigationPage, but not the MasterDetailPage (see #1, #2 and #3 below).
  2. Add an Icon to the MasterPage when on iOS (see #4 below). Don't forget to a the actual PNG(!) to the iOS resources.

The minimum working example is as follows:

public static class App
{
    static MasterDetailPage MDPage;

    public static Page GetMainPage()
    {
        return MDPage = new MasterDetailPage { // #1
            Master = new ContentPage {
                Title = "Master",
                Icon = Device.OS == TargetPlatform.iOS ? "menu.png" : null, // #4
                Content = new StackLayout {
                    Children = { Link("A"), Link("B"), Link("C") }
                },
            },
            Detail = new NavigationPage(new ContentPage { Content = new Label { Text = "A" } }), // #2
        };
    }

    static Button Link(string name)
    {
        var button = new Button { Text = name };
        button.Clicked += delegate {
            MDPage.Detail = new NavigationPage(new ContentPage { Content = new Label { Text = name } }); // #3
            MDPage.IsPresented = false;
        };
        return button;
    }
}
Anthropophagite answered 19/1, 2016 at 14:8 Comment(4)
Originally I posted this solution as an update to my question. Just now I decided to move it into an explicit answer.Anthropophagite
Is this App class is not same as the App class which extends the Application class ?Hurtless
@Krrish: When I found this solution (shortly after posting the question), this was the way to extend the Application class. Due to a Xamarin.Forms update, this changed quite a bit. See here for a more up-to-date example of master-detail pages.Anthropophagite
The icon was the key missing ingredient for me. Poof, the back button shows up.Cere
S
5

TL;DR

Essentially, your Detail page needs to be wrapped in a NavigationPage for the back button to appear in iOS.


Here's an example of how I structure my apps.

App.cs

    public static INavigation Navigation { get; set; }

    public static Page GetMainPage(IContainer container)
    {
        return new MainPage();
    }

MainPage.cs

public class MainPage : MasterDetailPage
{

    public MainPage()
    {
        Title = "Some Title";
        var master = new MainMenu();
        var detail = new NavigationPage(new FirstPage());

        if (App.Navigation == null)
        {
            App.Navigation = detail.Navigation;
        }

        Master = master;
        Detail = detail;
    }
}

Now that you've done this, your Navigation Drawer will behave as expected, and so will your ActionBar.

When you want to navigate throughout the app, you use the statically defined Navigation

await App.Navigation.PushAsync(new FooPage());
// or
await App.Navigation.PopAsync();
Somniferous answered 15/7, 2014 at 20:5 Comment(9)
Ok, doing the PushAsync on button.Clicked() opens a new detail page with a "Back" button, which - however - links back to the previous detail page, not the menu (a.k.a. master or MainMenu) as intended. Furthermore, the window.RootViewController.NavigationController is still null, thus I can't modify the button this way.Anthropophagite
What I'm trying to achieve is a similar behavior as in this video.Anthropophagite
what's in that video is the default behavior if you do what I did above. Your FirstPage would be the page with that image on it. Note you also have to set the icon on the MainMenu (Master) page.Somniferous
Sorry, but I can't achieve the desired behavior using your approach (see App.cs at GitHub). The "Back" button always brings me back to "FirstPage" and not the MainMenu.Anthropophagite
That's how it's designed to work, are you trying to reveal the navigation menu?Somniferous
Yes, that's what I want: A button to open the MainMenu - like it is in Android.Anthropophagite
@Anthropophagite - Did you succeed in android when hardware "Back" button is pressed? I have problem as it minimize the application instead of going back.Estelleesten
@SoftSan: In MainActivity you can override OnBackPressed setting IsPresented=false if it is currently true.Anthropophagite
isPresented is for showing/hiding menu (if I don't misunderstood), I do it in my NavigateTo Method inside masterdetail page. IsPresented = false; You can visit thisEstelleesten
E
1

Your on the right track, your NavigatePage needs to go on the Detail so

Detail = new ContentPage { Content = new Label { Text = "A" } }

and

MDPage.Detail = new ContentPage { Content = new Label { Text = name } };

would be

Detail = new NavigationPage(new ContentPage { Content = new Label { Text = "A" } })

and

MDPage.Detail = new NavigationPage(new ContentPage { Content = new Label { Text = name } });
Ecg answered 12/7, 2014 at 20:37 Comment(1)
This sounds very promising. (Since Android does not allow multiple navigation pages on screen at a time, I replaced return new NavigationPage(MDPage = ...) with return MDPage as well.) But now I see an icon on Android and the title of the Master page on iOS. And window.RootViewController.NavigationController is still null, thus I don't see how to further control the appearance of the toolbar or the back button, respectively.Anthropophagite
A
1

I finally found a solution. The code basically needs two minor corrections:

  1. Wrap all DetailPages in a NavigationPage, but not the MasterDetailPage (see #1, #2 and #3 below).
  2. Add an Icon to the MasterPage when on iOS (see #4 below). Don't forget to a the actual PNG(!) to the iOS resources.

The minimum working example is as follows:

public static class App
{
    static MasterDetailPage MDPage;

    public static Page GetMainPage()
    {
        return MDPage = new MasterDetailPage { // #1
            Master = new ContentPage {
                Title = "Master",
                Icon = Device.OS == TargetPlatform.iOS ? "menu.png" : null, // #4
                Content = new StackLayout {
                    Children = { Link("A"), Link("B"), Link("C") }
                },
            },
            Detail = new NavigationPage(new ContentPage { Content = new Label { Text = "A" } }), // #2
        };
    }

    static Button Link(string name)
    {
        var button = new Button { Text = name };
        button.Clicked += delegate {
            MDPage.Detail = new NavigationPage(new ContentPage { Content = new Label { Text = name } }); // #3
            MDPage.IsPresented = false;
        };
        return button;
    }
}
Anthropophagite answered 19/1, 2016 at 14:8 Comment(4)
Originally I posted this solution as an update to my question. Just now I decided to move it into an explicit answer.Anthropophagite
Is this App class is not same as the App class which extends the Application class ?Hurtless
@Krrish: When I found this solution (shortly after posting the question), this was the way to extend the Application class. Due to a Xamarin.Forms update, this changed quite a bit. See here for a more up-to-date example of master-detail pages.Anthropophagite
The icon was the key missing ingredient for me. Poof, the back button shows up.Cere

© 2022 - 2024 — McMap. All rights reserved.