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.

 

image
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:

toolbar_on_top

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:

 

image
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

toolbar_on_bottom

Need Xamarin Help?

Xamarin Consulting  Xamarin Training

As we mentioned in our Myths post (click to read), there’s a lot of hype as well as genuine concerns about using a public cloud to power mission-critical applications, especially those with high InfoSec or compliance requirements.

Data Breaches on Our Minds

It seems everyone is paying attention to security – especially on how to prevent data breaches. From developers – who, according to a recent Veracode survey, cite preventing data breaches and cyberattacks as their #1 concern – to service providers like Verizon who recently reported that web application attacks are the #1 source of data breaches – as IT professionals, we all have such concerns on our minds.

And it’s no wonder, according to IBM and Ponemon Institute (registration required to view) typical data breaches (under 100,000 records breached) cost US-based businesses:

  • An average of $7.01 million per data breach
  • An average of $221.00 per lost or stolen record

For Atmosera, Security is our Priority

We put security in the forefront of everything we do. Our methodology – whether on public, hybrid or private cloud services – from design through deployment focuses on delivering InfoSec and Compliance solutions that are realistically implementable.

Our services span the entire computing stack, from connectivity to applications, with stringent physical and logical security controls. We employ security professionals who uphold HIPAA/HITECH + HITRUST + PCI-DSS + IRS-1075 compliance thresholds, and our technology is secured end-to-end, while being actively monitored 24×7 every day of the year.

Secure Delete Application for Azure

We also actively develop applications when there’s an opportunity for something better in the market. We recently introduced the first secure delete app for Azure storage. The application was developed in response to the growing need to deliver better assurances that data is permanently deleted from cloud deployments. It uses the US Department of Defense (DoD) 5220.22-M data sanitization method.

Since there is no way to get to the physical drive, our secure delete application targets the logical sectors of the drives and stripes all data. This is an extreme measure, but ensures your data is deleted and not recoverable.

Successfully leveraging applications in a public cloud such as Azure requires careful planning, consideration and expertise to ensure it is properly setup with InfoSec in mind.

We welcome the opportunity to connect with you and talk through your needs.

Stay Informed

Sign up for the latest blogs, events, and insights.

We deliver solutions that accelerate the value of Azure.
Ready to experience the full power of Microsoft Azure?

Atmosera is thrilled to announce that we have been named GitHub AI Partner of the Year.

X