Toolbar Navigation in Xamarin Forms

It always amazes me when things that, as a framework developer, I would consider obvious are overlooked completely. Part of the promise of Forms is the ability to create a single definition and get a native feel as it gets applied to different platforms. Part of achieving this native feel is following the idiomatic standards of the platform. Today we are going to talk about one area of Forms that drives me nuts: ToolbarItems.

On iOS it is a COMMON use case that you have a ‘Cancel’ button to the left of the page title and some primary action to the right. For whatever reason, even though we are almost to 3.0 for Forms, the team has STILL not adopted this standard. Instead, we get a very cludgy and ugly system where the system puts ALL buttons to the right and, for overflow, creates a hideous toolbar beneath the title that I have never seen in a single iOS app.

Last night, while working through an app I am making I had to fight with this shortcoming and I think it makes sense to detail the approach I came to, based heavily on https://timeyoutake.it/2016/01/02/creating-a-left-toolbaritem-in-xamarin-forms/

Define the Toolbar Items

<ContentPage.ToolbarItems>
   <ToolbarItem Text="Save" Priority="0" Command="{Binding SaveCommand}" />
   <ToolbarItem Text="Cancel" Priority="1" Command="{Binding CancelCommand}" />
</ContentPage.ToolbarItems>

Here we are setting up. Priority denotes the order items are displayed on the right side. In our case, we need to use this so we can denote Order. Now, yes, I know there is an actual Order property on ToolbarItem and it would be great to use it. Sadly, we cant. Due to the design of ToolbarItem, Order overrides Priority thus, you end up with only a single menu item in the top and thus the approach we are going to use wont work. For now, assume 0 means Right, and 1 means Left – leave off Order. You cannot use it with the approach I am going to show.

The Custom Page

Forms being Forms, we are going to need a CustomRenderer to handle this special functionality. For this case I like to create a custom type to target with the renderer however, you dont have to do it; its a personal preference that I take.

<?xml version="1.0" encoding="UTF-8"?>
<controls:SubContentPage xmlns="http://xamarin.com/schemas/2014/forms"     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"     xmlns:controls="clr-namespace:GiftList.Controls"     x:Class="GiftList.Pages.ManageListItemPage"     BackgroundColor="{StaticResource BlueBackground}"     Title="{Binding PageTitle, Mode=OneWay}">
    <AbsoluteLayout HorizontalOptions="Fill" VerticalOptions="Fill">
    </AbsoluteLayout>

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Save" Priority="0" />
	<ToolbarItem Text="Cancel" Priority="1" />
    </ContentPage.ToolbarItems>
</controls:SubContentPage>

This is more or less a marker, nothing truly special is going on, I honestly could use the actual PageRenderer and it could be done the same.

Now the magic – the custom renderer (iOS only)

Since this idiom only exists on iOS we only need to write the custom renderer for iOS, Android can continue to use the default. This is a bit complex, so we will take it in chunks. Important: this is all happening in ViewWillAppear, do not use OnElementChanged – it will not work as you expect, you have to take this action AFTER Forms has rendered your view.

var navigationItem = NavigationController.TopViewController.NavigationItem;
var leftSide = new List<UIBarButtonItem>();
var rightSide = new List<UIBarButtonItem>();
var element = (ContentPage)Element;

This is all base assignment with a lot of coming from the link posted above. As you can see I am explicitly casting the associated Element to ContentPage, highlighting that I did not have to create the custom type.

The general goal here is to look at the items after the fact and reorganize them into Left and Right sides. Here is the code that does that, for loop is recommended due to the strange indexing that Xamarin does under the hood with ToolbarItems.

for (int i = 0; i < element.ToolbarItems.Count; i++)
{
    var offset = element.ToolbarItems.Count - 1;
    var item = element.ToolbarItems[offset - i];
    if (item.Priority == 1)
    {
        UIBarButtonItem barItem = navigationItem.RightBarButtonItems[i];
	barItem.Style = UIBarButtonItemStyle.Plain;
	leftSide.Add(barItem);
    }
    else
    {
        UIBarButtonItem barItem = navigationItem.RightBarButtonItems[i];
	barItem.Style = UIBarButtonItemStyle.Done;
	rightSide.Add(barItem);
    }
}

navigationItem.SetLeftBarButtonItems(leftSide.ToArray(), false);
navigationItem.SetRightBarButtonItems(rightSide.ToArray(), false);

Put simply, if the ToolbarItem has a Priority of 1 we assume it to be secondary and thus we place it to the left of the title using the native SetLeftBarButtonItems call. Notice that these calls allow us to place multiple buttons on each side – please dont do this, Apple wants the top bar kept clean to prevent clutter and confusion. If you have more options you can use an actual Toolbar within Apple.

So that’s it. That should work and you should get items on the Left and Right sides of the Title in iOS. Be careful modifying this thing for Android – I recommend heavy use of the <OnPlatform> tag.

Rant

Why this is not part of the main platform is beyond me. The general reasoning is that Android doesnt have the concept of a left icon. Putting aside the fact that it does, surely the Forms team has the capability to tailor this on a per platform basis. Maybe in 3.0. It just frustrates me to no end because this is something that platform should do, and there is little excuse, at this point, why it does not.

Advertisements

3 thoughts on “Toolbar Navigation in Xamarin Forms

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s