Xamarin Forms MasterDetailPage - Secondary menu drawn twice on iOS - xamarin

My app uses Master-Detail navigation.
I push pages on the navigation stack to have back button functionality.
When pushing a page with ToolbarItems (secondary menu), that's fine on Android, but it doubles the menu items on iOS.
Switching to the same page (not pushing), there is no problem on iOS (secondary menu shown once, as expected).
I adopted the official MasterDetail sample (https://github.com/xamarin/xamarin-forms-samples/tree/master/Navigation/MasterDetailPage) to show what I mean, see solution zip file.
Compile and start it on iOS device, press the button on the first page to open a sub page, and you should see doubled menu items (actually one which is drawn twice), so there are two rows of secondary menu items visible instead of just one.
The two rows are identical, both rows (and their menu items) react when tapping.
If you select the Reminder page through the Hamburger menu, the secondary menu is fine (even on iOS), as the page is not pushed on the navigation stack but switched.
Some interesting parts:
ContactsPage.xaml.cs:
SwitchToDetailPageAndPushOnNavStack()
ReminderPage.xaml:
<ContentPage.ToolbarItems>
<ToolbarItem Text="Do something" Clicked="OnDoSomethingClicked" Order="Secondary" />
</ContentPage.ToolbarItems>
What can I do to get rid of this weird effect on iOS?

It looks like you are adding a new NavigationPage which seems to be causing the issue. You block the extra nav bar with:
NavigationPage.SetHasNavigationBar(newPage, false);
but still the added toolbar item is getting added twice.
If instead you do the usual way of pushing a page, then this issue disappears. E.g. in ContactsPage.xaml.cs file, change:
private void OnOpenSubPageClicked(object sender, EventArgs args)
{
Device.BeginInvokeOnMainThread(() =>
{
MainPage mainPage = App.Instance.MainPage as MainPage;
if (null != mainPage)
mainPage.SwitchToDetailPageAndPushOnNavStack(mainPage.MasterPage.RemindersPageItem);
})
}
to:
private async void OnOpenSubPageClicked(object sender, EventArgs args)
{
await Navigation.PushAsync(new ReminderPage());
}
That seems to work as expected.

I had a similar issue. Posting here in case anyone else finds themselves in this bind.
I was getting the doubled toolbar when I had this code in App.cs...
MainPage = new NavigationPage(new MyApp.Home());
But changing it to
MainPage = new MyApp.Home();
solved the issue for me.

Related

Xamarin Shell: Redirect to different page and remove current page from nav stack

I imagine this is pretty straightforward, but I'm struggling to make sense of how to do it via the documentation...
I've got a tab page, that is opened from a flyout item.
The tabs for this page are dynamically loaded from saved data in the code behind, and all works exactly as expected.
However, in the scenario when there is no saved data (and thus, no tabs) I want to redirect the user to a different page, and crucially, not be able to navigate back to this tab page (but allow going back to the page they were initially on BEFORE they navigated to the tabbed page)
I've got a method in initalize for the tabbed page that, if it doesn't have any saved data, attempts to do this. I started with:
Shell.Current.Navigation.PopAsync();
Shell.Current.GoToAsync(nameof(AddNewDataPage));
which navigated to the add page, but pressing the back button just resulted in the add page being shown again, over and over.
I then tried:
Shell.Current.Navigation.PopToRootAsync();
Shell.Current.GoToAsync(nameof(AddNewDataPage));
which did the same.
So next I went with trying to navigate backwards:
Shell.Current.GoToAsync("../" + nameof(AddNewDataPage));
which showed the right page again, but now the back button doesn't work
Next, I went with trying an absolute route:
Shell.Current.GoToAsync("//HomePage/" + nameof(AddNewDataPage));
which worked... sort of.
The first time the user clicks the flyout for the tabbed page, it all works great. back button takes you to the home page etc... but the second and subsequent times the user clicked the flyout, they navigate to the tabbed page and my LoadData method isn't called.
I assumed this is because the tabbed page is still loaded, so I added:
protected override void OnAppearing()
{
LoadData();
}
Now, when the user clicks the flyout for the second and subsequent times, they navigate to the HomePage page instead of the AddNewDataPage page (an improvement, I guess?)
So, now I'm at a loss.. it seems like this should be really simple, but I can't figure it out.
You can try this.
//route The route hierarchy will be searched for the specified route, upwards from the current position. The matching page will replace the navigation stack.
https://learn.microsoft.com/nl-nl/xamarin/xamarin-forms/app-fundamentals/shell/navigation?WT.mc_id=friends-0000-jamont#relative-routes
await Shell.Current.GoToAsync($"//{nameof(AddNewDataPage)}");
https://www.youtube.com/watch?v=ylbgWHB_gMI&t=703s&ab_channel=JamesMontemagno
An explanation about relative routes.
May I don't understand your problem clearly. But I created a simple with the flyout shell app.
At first, it seems that the construction method of the content page will jut call once which the page shows first time. So I put the following code into it. Such as:
public ItemsPage()
{
InitializeComponent();
BindingContext = _viewModel = new ItemsViewModel();
Shell.Current.Navigation.PopAsync();
Shell.Current.GoToAsync(nameof(NewItemPage));
}
And then when the app got into the ItemsPage first time, it will get into the NewItemPage. User can add new item here. After that, when I clicked the back button, I will get into the ItemsPage back without it's construction method called. So you can't add the LoadData(); in it.
You can put it into the OnAppearing method. Such as:
protected override void OnAppearing()
{
//LoadData();
if (_viewModel.Items.Count < 8)
{
Shell.Current.Navigation.PopAsync();
Shell.Current.GoToAsync(nameof(NewItemPage));
// then add two item in the NewItemPage
}
else {
base.OnAppearing();
_viewModel.OnAppearing();
}
}
This will get the same effect as above. I also try to move the Shell.Current.Navigation.PopAsync();. The result is same. It will get back to the ItemsPage when you clicked the back button in the NewItemPage after you add two item.
which navigated to the add page, but pressing the back button just resulted in the add page being shown again, over and over.
You may just need a if() to judge the condition the app need to go to the AddNewDataPage with skipping the tab page.

Xamarin Forms Navigation.PushAsync works only once

In our Xamarin Forms app we have multiple pages. When we navigate to an other page for the first time from the main navigation page it works as expected. When we then use the back button (in the app or the Android system button) and navigate again to the next page then it doesn't load. It changes the title but it shows an empty page.
It does not matter what page you use as first page to go to and what page is the second page to go to. It also doesn't work if the first and second page is the same page.
In the app we initialize the main page as followed:
MainPage = new NavigationPage(new MainPage());
The MainPage is an TabbedPage.
We just navigate like this:
if (Application.Current.MainPage is NavigationPage mainPage)
{
await mainPage.Navigation.PushAsync(new DetailPage());
}
This worked before, but I think that during the update of Xamarin.Forms from 4.2.0.848062 to 4.3.0.947036 it broke. But I cannot find anything that could brake this is release notes.
== Edit ==
We saw some inconsistency in all the calls to navigate. So we created an helper class to do all the navigation. This now looks like this:
public static class NavigationHelper
{
public static void NavigateTo(Page page)
{
if (Application.Current.MainPage is NavigationPage mainPage)
{
Device.BeginInvokeOnMainThread(async () =>
{
await mainPage.Navigation.PushAsync(page);
});
}
}
}
So we now only navigate like this:
NavigationHelper.NavigateTo(new DetailPage());
But this still doesn't fix the issue. The first time we get the correct page, the second navigation we get an empty screen. The title in the navigation bar is changing to the page title where we navigate to. But the rest keeps empty.
Are you remembering to use the "Navigation" of the (NavigationPage)Application.Current.MainPage; - and not the current pages Navigation?
I've found it with some help. In the Output Window we found the message
System.ObjectDisposedException: Cannot access a disposed object.
This happened when we went back to the previous page and made it impossible to go to the next page. Turned out to be an bug in the view model. We where trying to add items to an observable list that was already disposed. Fixing this fixed the navigation issue.

When using navigationpage's InsertPageBefore method toolbar items disappear on iOS

When I navigate to a page that uses a flow of NavigationPage.Navigation.InsertPageBefore(ContentPage) then NavigationPage.PopAsync().ConfigureAwait(false) the toolbar item on the global navigation page disappears but when using push/pop the tool bar always remains. This only happens on iOS.
The navigation page is a global scoped to the app level along with a master detail page in which the navigation page serves as the detail portion.
Add ToolbarItem
var toobaritemMenu = new ToolbarItem
{
Icon = "Screenshot",
Command = new Command(() => Device.BeginInvokeOnMainThread(() => App.MasterNavigation.PushAsync(new ContentPage(), false)))
};
App.MasterNavigation.ToolbarItems.Add(toobaritemMenu);
Moving to a Page Like This Causes Toolbar Item to Disappear on iOS
Device.BeginInvokeOnMainThread(() => App.MasterNavigation.Navigation.InsertPageBefore(new ContentPage(), App.MasterNavigation.CurrentPage));
Device.BeginInvokeOnMainThread(() => App.MasterNavigation.PopAsync().ConfigureAwait(false));
Is there a reason for this as it seems inconsistent since it works on Android.
I ran into the same issue. My workaround was to change how I was manipulating the navigation stack: instead of inserting the new page and popping off the current page, push the new page onto the stack and then remove the current one:
await currentPage.Navigation.PushAsync(nextPage, animated: true);
currentPage.Navigation.RemovePage(currentPage);
You end up with the same final navigation stack, but it avoids whatever issue is causing the problem with the secondary toolbar items in iOS.

Load a Xaml inside a stacklayout in Xamarin forms

I have a Content View in Xamarin forms with 2 StackLayout horizontally aligned in it. I want to change the Content of the 2nd Stacklayout dynamically, but I don't want to use Master details page in that case. Please find an attachment to see what my UI looks like. I want to load different pages in StackLayout 2 on button clicks in StackLayout 1.
Update: I want to achieve above using MVVM.
You specifically said you want to load different pages inside Stacklayout2.
Out of the box, this is not possible. A Page can not be nested inside another view in Xamarin.Forms*
However, you most likely don't need to nest a whole page either, which is good news.
You can create custom xaml views as separate xaml files, and then reference them like regular controls. For example you would create a xaml file MyDataView, inside you could use a and fill it out with your different labels, entries and what nots, then instantiate and add that MyDataView inside your page like you would any other control.
For your host page, I would recommend you change your StackLayout2 to a ContentView, as it will only ever contain one view, which in case of something like your custom "MyDataView" from above, would actually contain the stack layout and all the details.
From the point of view of your page, it has the left layout with all the buttons, and it has the "container" on the right to host different complex views. So it does not need a stacklayout there.
There is also an important decision that you need to make on when and how you want to instantiate all the views that you will host inside the right pane.
You might choose to instantiate them all at once, when loading the page, if there aren't too many. Then switchig to each one during page use should be quite quick. Something like this:
public partial class MainPage
{
private MyDataView myDataView = new myDataView();
private OtherView otherView = new OtherView();
private ThirdView thirdView = new ThirdView();
public void OnSomeButtonClick(object sender, EventArgs e)
{
Container.View = myDataView; //Container would be the x:Name you gave to your ContentView control on the right side of the page that will host the different views
}
}
Or you might prefer to "lazy" instantiate the views, which would only instantiate the view the first time it is navigated to. This is useful if some views will never actually be accessed, and you can save some cpu cycles and ram by not loading the view until it's needed. The downside of course is when you do load it for the first time, it will load slower. Like this:
public partial class MainPage
{
private MyDataView myDataView;
private OtherView otherView;
private ThirdView thirdView;
public void OnSomeButtonClick(object sender, EventArgs e)
{
if (myDataView == null) myDataView = new MyDataView();
Container.View = myDataView; //Container would be the x:Name you gave to your ContentView control on the right side of the page that will host the different views
}
}
And finally you can instantiate the view everytime it is needed. I would not go with this route in most cases. Unless you really specifically need to recreate the entire view each time (like if you are dynamically changing it during use, and you need to reset it when it's shown again)
*I have seen a custom renderer implementation that nested a whole page inside a view on github. I did not test it as it was not completely implemented at the time.
Cant you do something like this:
button1.OnClick += (sender, args) =>{
StackLayout2.Children.Remove(currentview);
StackLayout2.Children.Add(newview);
}
Not sure if its called onclick or clicked.

Navigating back and forth not working

hi i created an app which contains three pages,
if go from MainPage to Page1 and reversely 3 to 4 times it ts working fine, but when i Navigate from Mainpage to Page 2 it is working but when i navigate back from Page 2 to Main Page it is opening MainPage after 1 second it is going back to Page2
can you please tell me the solution for this
Okay i am going to give this a wild guess seeing that you have not given code ,
on each page there is a override method you can use, onNavigatedTo.
what i will do is put an OnNavigatedTo event on each page and put a break point in that event and step to see what each individual page does, here is a quick example where i use onnavigated to test if the navigation to this page was a 'back' navigation!
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
//Check if navigation was back!
// you can add the breakpoint here if you want to.
if (e.NavigationMode == NavigationMode.Back)
{
messagebox.show("Nav is back!");
}
now you can easily see what each individual page does when navigated to :) hope this helps a bit.

Resources