Creating a Left ToolbarItem in Xamarin.Forms

TL;DR: If you’re looking for just the code, you can jump straight to it here.

I’ve been winding down 2015 by focusing on learning new subjects and improving my existing skills. Most of December was occupied with writing my first-ever app and I’ve been using Xamarin.Forms to create my UI. For those of you who are unfamiliar with it, Xamarin.Forms is a cross-platform toolkit for creating native user interfaces for iOS, Andriod, and Windows Phone apps. The native part is important here; simply put, I can code one UI, and Xamarin.Forms will do the heavy lifting for me, mapping my control as the equivalent control on each platform. If you are interested in how Xamarin.Forms works, I highly suggest reading the documentation, including Charles Petzold’s new book ‘Creating Mobile Apps with Xamarin.Forms‘.

One extremely useful feature of Xamarin.Forms is Custom Renderers. If there is something the toolkit doesn’t provide, or if you want to change a default appearance or behavior, you can create a custom renderer and voilà, your needs have been met!

While writing my app one problem I had was creating a lefthand bar button for a modal page on iOS, which is not possible by default in Xamarin.Forms.

I have a screen in my application which allows the user to add Items to a Pantry , and is presented modally as so:

void OnAdd (object sender, EventArgs e)
{
    Navigation.PushModalAsync (new NavigationPage (new ItemPage ()));
}

I want to have the user explicitly save, or cancel, the information they have entered. I don’t want to use a simple push transition that would show a back button; it’s not the right paradigm for what I’m trying to do. iOS allows this modal choice, as evidenced when adding a new contact:IMG_0197

(Android uses the device ‘back’ button in this instance, but right now I’m just concerned about iOS.)

So, now that I’ve created the transition to my modal screen–an instance of ItemPage–I need to add a couple of ToolbarItems in the ItemPage’s XAML:

<ContentPage.ToolbarItem>
	<ToolbarItem Text="Save" Activated="OnSave" Priority="0" Order="Primary" />
	<ToolbarItem Text="Cancel" Activated="OnCancel" Priority="1" Order="Primary" />
</ContentPage.ToolbarItems>

If I run the app in the iOS simulator I notice that all ToolbarItems render on the right hand side of the navigation bar, and herein lies my problem:

ToolBarITems

Xamarin.Forms does not let you change the position of the ToolbarItems, but that isn’t enough to stop me me adjusting it. Fortunately, custom renderers save the day!

Creating the ToolbarItem Custom Renderer

I’m not going to use this post to explore creating custom renderers as Xamarin’s documentation already does a great job of explaining how to create and use them. If you are unfamiliar with them, I recommend reading the docs.

Inspired by this forum post, I want to use the Priority value of each ToolbarItem to determine which side of the Navigation Item it will display on. Each item with Priority = 0 will go on the right side of the bar, and each item with Priority = 1 will go on the left side. Here’s the code:

[assembly:ExportRenderer (typeof(FridgeContentPage), typeof(FridgeContentPageRenderer))]
namespace Fridge.iOS
{

    public class FridgeContentPageRenderer : PageRenderer
    {
        public new FridgeContentPage Element {
            get{ return (FridgeContentPage)base.Element; }
        }

    public override void ViewWillAppear (bool animated)
    {
        base.ViewWillAppear (animated);

        var LeftNavList = new List<UIBarButtonItem> ();
        var rightNavList = new List<UIBarButtonItem> ();

        var navigationItem =this.NavigationController.TopViewController.NavigationItem;

        for (var i = 0; i < Element.ToolbarItems.Count; i++) {

            var reorder = (Element.ToolbarItems.Count - 1);
            var ItemPriority = Element.ToolbarItems [reorder - i].Priority;

            if (ItemPriority == 1) {
                UIBarButtonItem LeftNavItems = navigationItem.RightBarButtonItems [i];
                LeftNavList.Add (LeftNavItems);
            } else if (ItemPriority == 0) {
                UIBarButtonItem RightNavItems = navigationItem.RightBarButtonItems [i];
                rightNavList.Add (RightNavItems);
            }
        }

            navigationItem.SetLeftBarButtonItems (LeftNavList.ToArray (), false);
            navigationItem.SetRightBarButtonItems (rightNavList.ToArray (), false);

    }
  }
}

Great…so what’s going on here?

First, I used the attribute, shown below, to tell Xamarin.Forms that we are using a custom renderer. This attribute takes two parameters: the type name of the Xamarin.Forms page being rendered (In our example FridgeContentPage), and the type name of the custom renderer (FridgeContentPageRenderer in this example).

[assembly:ExportRenderer (typeof(FridgeContentPage), typeof(FridgeContentPageRenderer))]

Next, I created two lists to hold my left and right UIBarButtonItems (the native name for ToolBarItems in iOS):

var LeftNavList = new List<UIBarButtonItem> ();
var rightNavList = new List<UIBarButtonItem> ();

barbuttonitems

I need to have some way to get the items from the right side, and either set them to the left side, or keep them on the right, depending on their Priority. The UINavigationItem in iOS has some properties that will let me get and set these, so I’ll create a variable to capture my navigation item:

var navigationItem = this.NavigationController.TopViewController.NavigationItem;

I then want to go through each of my ToolbarItems and check its priority. If it has Priority = 1, it will be add it to the leftNavList list; if Priority = 0 it’ll be added to the rightNavList. This is achieved through a simple for loop:

for (var i = 0; i < Element.ToolbarItems.Count; i++) {

    ...
    var ItemPriority = Element.ToolbarItems[i].Priority;

    if (ItemPriority == 1) {
        UIBarButtonItem LeftNavItems = navigationItem.RightBarButtonItems [i];
        leftNavList.Add (LeftNavItems);
    } else if (ItemPriority == 0) {
        UIBarButtonItem RightNavItems = navigationItem.RightBarButtonItems [i];
        rightNavList.Add (RightNavItems);
    }
 }

At this point in the code, I have two lists containing left and right items which need to be placed on their respective side of the NavigationItem. As I mentioned earlier, UINavigationItem has properties for this purpose – SetLeftBarButtonItems & SetRightBarButtonItems – which take an array as input. I can use the following lines of code to achieve this:


navigationItem.SetLeftBarButtonItems (leftNavList.ToArray (), false);
 navigationItem.SetRightBarButtonItems (rightNavList.ToArray (), false);

Hooray! We are almost finished… except for one little problem. If I run this in the iOS simulator now, the ToolbarItems won’t be in the expected place. The Save button with Priority = 0 is on the left, the Cancel button with Priority = 1 is on the right:

Screen Shot 2015-12-28 at 9.48.57 PM

Why is this happening?

It’s because ToolbarItems (from Xamarin.Forms) stores the items in a reverse order to RightBarButtonItems (from iOS). Therefore the the item (i) referenced in the ItemPriority variable is not the same as the one that we will be assigning to the LeftNavItems and RightNavItems variables. To get around this, I can reverse the order of the ToolbarItems so that the same ToolbarItem corresponds to the same RightBarButtonItem by adding the follow to the start of my for loop:


 var reorder = (Element.ToolbarItems.Count - 1);
 var ItemPriority = Element.ToolbarItems [reorder - i].Priority;

Finally, if we run the application now:

Screen Shot 2015-12-28 at 10.12.40 PM

Huzzah! It works as expected.

Of course there are improvements that could be made to this custom renderer; null-checking jumps out as a big one. This is where I encourage you, dear reader, to use my code and make it better!

Happy custom rendering!

Amy

24 thoughts on “Creating a Left ToolbarItem in Xamarin.Forms

  1. I tested in X.F. 2.3.0.49 and works perfectly. Very easy to understand and apply.

    A doubt: you say that in Android devices, the users use hardware button to go back. So, in this case, not is recomended implement custom render for Android to provide left button in this platform?

    Thank you for your article, is very helpful!

    Like

  2. @Amy

    This is by far the easiest solution for me to implement and works well. Thank you for sharing this with the Xamarin community.

    Not sure when you last visited this but there is an unusual problem when one pushes a new page from this Modal page using Navigation.PushAsync(myNextPage)

    Clicking on the Nav Bar back button labelled ‘myNextPage’ throws an exception error
    ‘Index was outside the bounds of the array’

    Is the CustomRenderer somehow interfering with the default back button on he left of the Nav Bar?

    Like

    • For anyone else who may stumble across this blog page. The defect that exists in this renderer is that it assumes there are always two RightBarButtonItems. However if you ‘Navigation.Push’ or ‘Navigation.PushModal’ another Page onto the Stack and then navigate back, the Custom Renderer is called again and crashes. This is because you have already moved the ‘Cancel’ button to the left when the Page was first created.

      The simplest method (&quickest) method for me was to add a line after

      var navigationItem =this.NavigationController.TopViewController.NavigationItem;

      which will exit the Renderer if only one RightBarButton item exists

      if (navigationItem.RightBarButtonItems.Length < 2)
      return;

      Like

  3. please help..
    1. my dashboard already have 2 toolbar item.
    2. I need to redirect to another page (using INavigatedAware). After i click back, the apps will crash.
    3. The error said “Index was outside the bounds of the array.”

    please help. thanks

    Like

  4. Hi i want to remove space infront of left item(Cancel) and back side of right item(Save),i am working with xamarin.forms ,could you please help me on this ,thanks in advance.

    Like

  5. Trying to find powerful online promotion that isn’t full of crap? I apologize for sending you this message on your contact form but actually that’s exactly where I wanted to make my point. We can send your promotional copy to sites through their contact pages just like you’re reading this ad right now. You can specify targets by keyword or just fire off bulk blasts to sites in any country you choose. So let’s assume you need to push through a message to all the plumbing companies in the US, we’ll scrape websites for just those and post your ad text to them. As long as you’re promoting a product or service that’s relevant to that business category then you’ll receive awesome results!

    Type up a quick note to sarah1916eva@gmail.com for the details

    Like

  6. Trying to find effective advertising that isn’t completely full of it? I apologize for sending you this message on your contact form but actually that was kinda the point. We can send your advertising text to websites via their contact pages just like you’re receiving this note right now. You can target by keyword or just execute mass blasts to sites in the location of your choice. So let’s assume you want to push through a message to all the real estate agents in the United States, we’ll scrape websites for only those and post your ad text to them. Providing you’re advertising some kind of offer that’s relevant to that type of business then your business will get an amazing response!

    Shoot an email to muhammad2435tay@gmail.com for details on how this works

    Like

  7. Are you seeking powerful online promotion that has no per click costs and will get you new customers fast? Sorry to bug you on your contact form but actually that’s exactly where I wanted to make my point. We can send your ad message to websites via their contact pages just like you’re reading this message right now. You can target by keyword or just do bulk blasts to sites in the country of your choice. So let’s assume you want to blast an ad to all the web developers in the US, we’ll grab websites for only those and post your advertisement to them. Providing you’re promoting something that’s relevant to that niche then your business will get an amazing response!

    Write a quickie email to poppy8542bro@gmail.com to find out how we do this

    Like

  8. Hi, I was just on your website and filled out your contact form. The contact page on your site sends you these messages via email which is the reason you’re reading through my message at this moment correct? That’s the most important achievement with any type of online ad, getting people to actually READ your message and that’s exactly what I just accomplished with you! If you have an advertisement you would like to blast out to thousands of websites via their contact forms in the U.S. or anywhere in the world let me know, I can even target specific niches and my costs are very affordable. Reply here: Benja57mik57@gmail.com

    Like

Leave a comment