Blog

Creating a Secondary (bottom) iOS Toolbar in Xamarin Forms

Xamarin Forms is a really great platform for mobile app development – we have used it on several apps now and had much better results than when trying to use the native SDK’s directly. Every now and then though you run up against a roadblock with the default renderer implementations where some feature (perhaps a key feature of your app) simply does not work the way it should. Secondary toolbars on iOS are one of those. I recently spent a couple of days trying to coax this feature into working properly before finally finding a solution (many other folks seemed to have simply given up on it).

What is a secondary toolbar?

Xamarin Forms supports the notion of Page-level toolbars. These are represented by the Page.ToolbarItems collection, and each ToolbarItem represents a clickable button that is hosted within the toolbar. In fact, there are two toolbars – the main “primary” toolbar that fills the right end of the navigation bar at the top of your screen, and also a “secondary” toolbar. You can only fit a few (two or three at most) toolbar items on the primary toolbar, and the rest are generally expected to go into the secondary toolbar. On Android this is done by adding an expansion button to the far right end of the primary toolbar which drops down a vertical menu containing the secondary toolbar items. That’s a fine and reasonable implementation for Android. On iOS, there is a native “bottom” toolbar that is intended to be used for this type of thing. But Xamarin Forms doesn’t use that native toolbar and instead creates its own custom panel below the primary toolbar and renders some rather ugly buttons there. Not only does this look very out of place in an iOS app, but it also prevents you from using the built-in system toolbar icons.

 

Native secondary toolbar in iOS (appears at the bottom of the screen)

 

In my case, I desperately wanted to have a bottom toolbar with the iOS “Action” built-in system icon (usually used for “export” or “send” or “share” functions). The default Xamarin Forms behavior was not an acceptable substitute. I explored many options – including implementing my own ToolbarItems on a custom Page class – but this ended up being the best solution (and actually the only solution that worked in 100% of my test cases).

Ultimately, I settled on a solution that requires only a pair of custom renderers and no hacking up of your XAML pages.

An Example App

In order to demonstrate this solution, let’s create an extremely simple example app. I created a new Xamarin Forms app in Xamarin Studio (only bothering to select iOS platform – no need to add an Android project for this demo). To this app, I added a new Content Page (XAML) named MainAppPage, with some extremely simple markup. No changes were made to the code-behind – this is just a very simple page:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns_x="http://schemas.microsoft.com/winfx/2009/xaml" x_Class="SecondaryToolbarDemo.MainAppPage" Title="Main"></pre>
<pre><code>&lt;ContentPage.ToolbarItems&gt;
    &lt;ToolbarItem Text=&quot;Share&quot; Command=&quot;{Binding ShareCommand}&quot;/&gt;
&lt;/ContentPage.ToolbarItems&gt;

&lt;Image Source=&quot;http://lorempixel.com/320/480/abstract&quot; Aspect=&quot;AspectFill&quot;/&gt;
</code></pre>
<pre></ContentPage> 

As an aside, http://lorempixel.com/ is a fantastic way to add placeholder artwork to an app!

My App class was then changed to the following very straightforward implementation. This just wraps the previous page with a Navigation container and also provides a Command to be bound to the Share toolbar item. Clicking Tapping the toolbar item will display a plain alert. Here is the full code for the App class (we won’t need to change it again):

    public class App : Application
    {
        public Command ShareCommand { get; private set; }</pre>
<pre><code>    public App ()
    {
        ShareCommand = new Command (OnShare);
        MainPage = new NavigationPage (new MainAppPage { BindingContext = this });
    }

    void OnShare()
    {
        MainPage.DisplayAlert (&quot;Toolbar Demo&quot;, &quot;Hello from the toolbar!&quot;, &quot;WHATEV&quot;);
    }
}
</code></pre>
<pre>

When we run this in the simulator, as expected we get a simple page with a Share toolbar button. Since the default behavior is to place toolbar items into the primary location, this button appears at the top right end of the navigation bar. Try it, it should work:

Switching to the Secondary Toolbar

Now, let’s see what happens when we specify that this toolbar item should go into the secondary toolbar. We do this by adding a simple Order="Secondary" attribute to the toolbar item. But as mentioned above, this produces something that isn’t so pleasant:

 

The default secondary toolbar created by Xamarin Forms in iOS

 

Xamarin Forms is doing a few things here. First, they are creating their own UIToolbar using a private nested class (so you can’t really do anything about it). Their toolbar uses custom UIViews to display the toolbar items, and you don’t really get much control over the process or visual styling. In addition to this, they perform some view layout operations that assume nobody else is tinkering with the subviews that the default renderer creates, and lastly this code is split up between two different places – the Page renderer as well as the NavigationPage renderer.

So let’s see about fixing the situation.

Fixing the NavigationPage Renderer

First of all, let’s fix the default renderer for NavigationPage. There are two things going on in the default renderer that contribute to this problem – first, the NavigationPage is where we will find that ugly and unsavory private UIToolbar. Second, the default renderer performs some unfriendly layout logic that won’t play well with our attempts to tweak things. But we can (and will) defeat it.

The first order of business is to ditch that ugly UIToolbar before it ever gets shown. How do we find it? Easy – it will be the only UIToolbar-derived subview that isn’t actually a UIToolbar (remember, they inherited that private class from it). If we find it, we pluck it out from the visual tree. This can be done easily within ViewWillAppear() of the custom NavigationPage renderer:

        public override void ViewWillAppear (bool animated)
        {
            var badBar = View.Subviews.OfType<UIToolbar> ().FirstOrDefault (v => v.GetType () != typeof(UIToolbar));
            if (badBar != null) {
                badBar.RemoveFromSuperview ();
            }
            base.ViewWillAppear (animated);
        }

And of course we also want to correct the bad layout logic. That’s also easy enough because we too can override ViewDidLayoutSubviews() and pretty much ignore what the base renderer wanted to do, and enforce our own layout instead. Essentially, if we see any UIToolbars then we want to let them self-position themselves at the bottom (there shouldn’t be more than one), and then resize anything else (the page’s content) to fill the remaining area. Easy enough:

        public override void ViewDidLayoutSubviews ()
        {
            base.ViewDidLayoutSubviews ();</pre>
<pre><code>        UIView[] subviews = View.Subviews.Where (v =&gt; v != NavigationBar).ToArray ();
        var toolBarViews = subviews.Where (v =&gt; v is UIToolbar).ToArray ();
        var otherViews = subviews.Where (v =&gt; !(v is UIToolbar)).ToArray ();

        nfloat toolbarHeight = 0;

        foreach (var uIView in toolBarViews) {
            uIView.SizeToFit ();
            uIView.Frame = new CGRect {
                X = 0,
                Y = View.Bounds.Height - uIView.Frame.Height,
                Width = View.Bounds.Width,
                Height = uIView.Frame.Height,
            };
            var thisToolbarHeight = uIView.Frame.Height;
            if (toolbarHeight &lt; thisToolbarHeight) {
                toolbarHeight = thisToolbarHeight;
            }
        }

        var othersHeight = View.Bounds.Height - toolbarHeight;
        var othersFrame = new CGRect (View.Bounds.X, View.Bounds.Y, View.Bounds.Width, othersHeight);

        foreach (var uIView in otherViews) {
            uIView.Frame = othersFrame;
        }
    }
</code></pre>
<pre>

Fixing the Page Renderer

Our customized Page renderer needs to be a little more hackish. We first want to ensure that the base renderer doesn’t ever try to process any “secondary” toolbar items, so we need to extract them from the page’s ToolbarItems collection whenever the renderer attaches to it. Then, just prior to displaying the native view, we want to create our own UIToolbar and populate it with buttons built from those captured toolbar items. Our toolbar will then get noticed by the NavigationPage renderer above, and get placed appropriately. We also need to be sure to remove our UIToolbar when the page’s view disappears – otherwise the toolbar would remain visible after navigating away.

And of course, all of that would be for nothing if we didn’t actually wire up the toolbar buttons to some actions that ultimately find their way to the source Command of the corresponding ToolbarItems. There are several ways to do this, and originally I was using inline lambda handlers, but the AOT compiler was choking on that (I think due to the hoisted local var being used within a native callback – I’ve experienced other similar cases of AOT compiler crashes)… so I settled on using a dictionary to map UIBarButtonItems back to their corresponding Commands. Of course the downside of this approach is that it only works with ToolbarItems which are using Command bindings (not Clicked events)… but surely anyone using Xamarin Forms is also using some form of MVVM and Command binding right? I mean after all, we aren’t a bunch of VB savages!

    public class ToolbarRenderer : PageRenderer
    {
        UIToolbar _toolbar;
        List<ToolbarItem> _secondaryItems;
        readonly Dictionary<UIBarButtonItem, ICommand> _buttonCommands = new Dictionary<UIBarButtonItem, ICommand> ();</pre>
<pre><code>    protected override void OnElementChanged (VisualElementChangedEventArgs e)
    {
        var page = e.NewElement as Page;
        if (page != null) {
            _secondaryItems = page.ToolbarItems.Where (i =&gt; i.Order == ToolbarItemOrder.Secondary).ToList ();
            _secondaryItems.ForEach (t =&gt; page.ToolbarItems.Remove (t));
        }
        base.OnElementChanged (e);
    }

    public override void ViewWillAppear (bool animated)
    {
        if (_secondaryItems != null &amp;&amp; _secondaryItems.Count &gt; 0) {
            var tools = new List&lt;UIBarButtonItem&gt; ();
            _buttonCommands.Clear ();
            foreach (var tool in _secondaryItems) {
                #pragma warning disable 618
                var systemItemName = tool.Name;
                #pragma warning restore 618
                UIBarButtonItem button;
                UIBarButtonSystemItem systemItem;
                button = Enum.TryParse&lt;UIBarButtonSystemItem&gt; (systemItemName, out systemItem) 
                    ? new UIBarButtonItem (systemItem, ToolClicked) 
                    : new UIBarButtonItem (tool.Text, UIBarButtonItemStyle.Plain, ToolClicked);
                _buttonCommands.Add (button, tool.Command);
                tools.Add (button);
            }

            NavigationController.SetToolbarHidden (false, animated);
            _toolbar = new UIToolbar (CGRect.Empty) { Items = tools.ToArray () };
            NavigationController.View.Add (_toolbar);
        }

        base.ViewWillAppear (animated);
    }

    void ToolClicked(object sender, EventArgs args)
    {
        var tool = sender as UIBarButtonItem;
        var command = _buttonCommands [tool];
        command.Execute (null);
    }

    public override void ViewWillDisappear (bool animated)
    {
        if (_toolbar != null) {
            NavigationController.SetToolbarHidden (true, animated);
            _toolbar.RemoveFromSuperview ();
            _toolbar = null;
            _buttonCommands.Clear ();
        }
        base.ViewWillDisappear (animated);
    }
}
</code></pre>
<pre>

One final thing to point out is that I am also abusing the obsolete Name property of ToolbarItem. Since ToolbarItem doesn’t provide a means of specifying that a certain built-in icon should be used instead of the Text label, I am using the Name property to pass that information to my custom renderer. If Name has a value, then it tries to parse that value as a UIBarButtonSystemItem. Otherwise, it treats the toolbar item as a normal text-based button. Normally I would have create an attached XAML property to use for conveying this information, but the existence of the obsolete Name property gave me a shortcut in this case.

Also, hint / wink / nudge to anyone on the Xamarin Forms team who might read this – it sure would be handy in some situations if there were just a simple “Tag” property added to the base VisualElement or Element class.

The Final App

Anyhow, after introducing our two renderers (and adding the appropriate Order and Name attributes to the ToolbarItem in our XAML code), the final application works great as you can see below!

Full source code for this example project can be found here: https://github.com/Wintellect/XamarinSamples/tree/master/SecondaryToolbarDemo

Need Xamarin Help?

Xamarin Consulting  Xamarin Training

Keith Rome

View Comments

  • Does something need to be done to tell the Xamarin.Forms Page that it no longer extends to the bottom of the screen? I popped those two renderers into an app that I am developing and the result is that items that previously were at the bottom of the Page now seem to be hidden behind the toolbar.

  • Does something need to be done to tell the Xamarin.Forms Page that it no longer extends to the bottom of the screen? I popped those two renderers into an app that I am developing and the result is that items that previously were at the bottom of the Page now seem to be hidden behind the toolbar.

  • Works well, thank you. I am also have the same issue as JH, stuff on the page gets hidden behind the bottom toolbar.

  • Works well, thank you. I am also having the same issue as JH, stuff on the page gets hidden behind the bottom toolbar.

    • I had this issue but had 2 NavigationRenderers in my project. Consolidated this to 1 renderer and it all seems to work well.

  • I had this issue but had 2 NavigationRenderers in my project. Consolidated this to 1 renderer and it all seems to work well.

Recent Posts

8-Step AWS to Microsoft Azure Migration Strategy

Microsoft Azure and Amazon Web Services (AWS) are two of the most popular cloud platforms.…

1 day ago

How to Navigate Azure Governance

 Cloud management is difficult to do manually, especially if you work with multiple cloud…

1 week ago

Why Azure’s Scalability is Your Key to Business Growth & Efficiency

Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…

3 weeks ago

Unlocking the Power of AI in your Software Development Life Cycle (SDLC)

https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…

1 month ago

The Role of FinOps in Accelerating Business Innovation

FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…

1 month ago

Azure Kubernetes Security Best Practices

Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…

2 months ago