Overview
In Xamarin.Forms on iOS it’s not as easy as it should be to hide TabBar on navigation. In native programming there is a property HidesBottomBarWhenPushed, unfortunately it doesn’t work with Xamarin.Forms.
The main idea to solve this issue is to set TabBar’s height to zero and hide it, but the problem is that frame modification causes a blank space in place of TabBar. You can see it below:
1. Custom TabbedPage implementation
To fix the solution above we need to layout all ChildViewControllers, so let’s start with implementing our HideableTabbedPage with IsHidden property and its Renderer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System; using Xamarin.Forms; namespace HideTabBar.Controls { public class HideableTabbedPage : TabbedPage { public static readonly BindableProperty IsHiddenProperty = BindableProperty.Create(nameof(IsHidden), typeof(bool), typeof(HideableTabbedPage), false); public bool IsHidden { get { return (bool)GetValue(IsHiddenProperty); } set { SetValue(IsHiddenProperty, value); } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
using System; using System.ComponentModel; using System.Threading.Tasks; using HideTabBar.Controls; using HideTabBar.iOS.CustomRenderer; using UIKit; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; [assembly: ExportRenderer(typeof(HideableTabbedPage), typeof(HideableTabbedPageRenderer))] namespace HideTabBar.iOS.CustomRenderer { public class HideableTabbedPageRenderer : TabbedRenderer { private bool disposed; private const int TabBarHeight = 49; protected override void OnElementChanged(VisualElementChangedEventArgs e) { base.OnElementChanged(e); if (e.OldElement == null) { this.Tabbed.PropertyChanged += Tabbed_PropertyChanged; } } private void Tabbed_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == HideableTabbedPage.IsHiddenProperty.PropertyName) { this.OnTabBarHidden((this.Element as HideableTabbedPage).IsHidden); } } protected override void Dispose(bool disposing) { base.Dispose(disposing); this.disposed = true; } private async void OnTabBarHidden(bool isHidden) { if (this.disposed || this.Element == null || this.TabBar == null) { return; } await this.SetTabBarVisibility(isHidden); } private async Task SetTabBarVisibility(bool hide) { this.TabBar.Opaque = false; if (hide) { this.TabBar.Alpha = 0; } this.UpdateFrame(hide); // Show / Hide TabBar this.TabBar.Hidden = hide; this.RestoreFonts(); // Animate appearing if (!hide) { await UIView.AnimateAsync(0.2f, () => this.TabBar.Alpha = 1); } this.TabBar.Opaque = true; this.ResizeViewControllers(); this.RestoreFonts(); } private void UpdateFrame(bool isHidden) { var tabFrame = this.TabBar.Frame; tabFrame.Height = isHidden ? 0 : TabBarHeight; this.TabBar.Frame = tabFrame; } private void RestoreFonts() { // Workaround to restore custom fonts: foreach (var item in this.TabBar.Items) { var text = item.Title; item.Title = ""; item.Title = text; } } private void ResizeViewControllers() { foreach (var child in this.ChildViewControllers) { child.View.SetNeedsLayout(); child.View.SetNeedsDisplay(); } } } } |
2. Auto hiding implementation
Now to automatically hide TabBar we will use a custom Page implementation. To make it simple I assumed that Application.Current.MainPage is HideableTabbedPage. If you want to improve this solution, you can hide TabBar using for example MessagingCenter.
1 2 3 4 5 6 |
using Xamarin.Forms; namespace HideTabBar.Controls { public class TabDetailPage : ContentPage { } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using HideTabBar.Controls; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; [assembly: ExportRenderer(typeof(TabDetailPage), typeof(HideTabBar.iOS.CustomRenderer.TabDetailPageRenderer))] namespace HideTabBar.iOS.CustomRenderer { public class TabDetailPageRenderer : PageRenderer { public override void ViewWillDisappear(bool animated) { base.ViewWillDisappear(animated); // Show TabBar when main page is displayed if (this.NavigationController.ViewControllers.Length == 1) { (Xamarin.Forms.Application.Current.MainPage as HideableTabbedPage).IsHidden = false; } } public override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); // Hide TabBar when details are displayed if (this.NavigationController.ViewControllers.Length > 1) { (Xamarin.Forms.Application.Current.MainPage as HideableTabbedPage).IsHidden = true; } } } } |
3. Sample usage
1 2 3 4 5 6 7 8 9 10 |
<!--?xml version="1.0" encoding="utf-8"?--> <controls:hideabletabbedpage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:views="clr-namespace:HideTabBar.Views" xmlns:controls="clr-namespace:HideTabBar.Controls" x:class="HideTabBar.Views.MainPage"> <controls:hideabletabbedpage.children> <navigationpage title="Browse"> <x:arguments> <views:itemspage> <!-- This needs to inherit from TabDetailPage --> </views:itemspage></x:arguments> </navigationpage> </controls:hideabletabbedpage.children> </controls:hideabletabbedpage> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System; using HideTabBar.Controls; using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace HideTabBar.Views { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class MainPage : HideableTabbedPage { public MainPage() { InitializeComponent(); } } } |