简体   繁体   中英

How to hide Tab Bar on push in Xamarin.Forms?

I'm struggling for last few days with TabbedPage in Xamarin.Forms on iOS. I found some solutions like those: https://forums.xamarin.com/discussion/20901/hide-tab-bar-on-push

However, none of them works well. I also tried to subclass TabbedRenderer and set TabBar height to 0. It works, but if I hide TabBar in NavigationPage.Pushed event handler, there's some delay and for example TableView has blank space on the bottom.

If I try to override NavigationRenderer and hide/show Tab Bar in PushViewController / PopViewController methods it sometimes fails. For example if I navigate fast back and forth, method PopViewController is not invoked, NavigationStack is broken and Tab Bar is not restored.

I think that the only good solution would be to make this property work: UIViewController.HidesBottomBarWhenPushed . However, I have no idea how to do it, because setting/overriding it in renderers doesn't work.

Did anybody manage to successfuly show & hide TabBar?

I managed to implement a solution which fixes the issue with blank space after hiding TabBar . You can read more details about it in this article .

To solve the problem we just need to layout all ChildViewControllers . Here is my sample implementation of a custom TabbedPage and its TabbedPageRenderer .

HideableTabbedPage.cs:

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); }
        }
    }
}

HideableTabbedPageRenderer.cs:

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();
            }
        }
    }
}  

Final result:

自动隐藏标签栏

What I have tried :

  • Create subclass of ContentPage and create BindableProperty(like HidesBottomBarWhenPushed ) inside it. I set ViewController.hidesBottomBarWhenPushed in PageRenderer but it doesn't work, although I can get the value of this property .

  • set this.hidesBottomBarWhenPushed in the initial constructor in PageRenderer , still no luck.

I think it must be something wrong with hidesBottomBarWhenPushed , we can not hide tabbar by this way. As a temporary and simple workaround , I change the Visible of TabBarController.TabBar in PageRenderer

class PageiOS : PageRenderer
{
    public override void ViewWillAppear(bool animated)
    {
        base.ViewWillAppear(animated);
        if (this.NavigationController != null && this.TabBarController != null)
        {
            bool isRootVC = this.NavigationController.ViewControllers.Length == 1;
            ParentViewController.TabBarController.TabBar.Hidden = !isRootVC;
        }
    }
}

It behaves like what you said above , there is some delay and blank space on the bottom . I disable the animation on the push and pop , and the issue disappeared.

Test:

在此处输入图片说明

I have faced with an issue when I need to hide tab bar before it drawn on the screen.

The solution from Wojciech Kulik helped me but it started blinking on navigating to the tabbed page.

The code below solved my problem. Hope it'll be helpful for you. Place it in your TabbedRenderer derived class

public override void ViewWillLayoutSubviews()
{
    OnTabBarHidden(true); // Hide before the page appear
}

There is a solution that doesn't require any renders and works on both Android and iOS.

Wrap the TabbedPage in a NavigationPage so the structure of your app becomes

  • NavigationPage (root)
    • TappedPage
      • NavigationPage
        • ContentPage (with tabbar)
    • ContentPage (without tabbar)

On the TabbedPage you have to hide the navigationbar of the 'root' NavigationPage, otherwise you have 2 navbars.

<TabbedPage
    ...
    HasNavigationBar="False"> 

If you push a page using the 'root' NavigationPage , the tabbar is hidden and there is no blank space at the bottom.

See my example at: https://github.com/Jfcobuss/HideTabbarExample/tree/master/HideTabbarExample

I did manage to attain the desired result in a more "native way" using HidesBottomBarWhenPushed property. If you take a look into how navigation is implemented for iOS you would notice, that before pushing a new view controller into navigation stack it gets wrapped in a parent view controller inside CreateViewControllerForPage method. It gave me a chance to intercept already created view controller that is not yet pushed to the navigation stack and set HidesBottomBarWhenPushed property. For that purpose I've created an empty class derived from ContentPage :

public class NoTabBarPage : ContentPage { }

So now every page derived from the NoTabBarPage wan't have bottom tab bar.

And also a renderer that makes everything work:

[assembly: ExportRenderer(typeof(NoTabBarPage), typeof(NoTabBarPageRenderer))]
namespace HideTabBar.iOS.Renderers
{
    public class NoTabBarPageRenderer : PageRenderer
    {
        public override void DidMoveToParentViewController(UIViewController parent)
        {
            base.DidMoveToParentViewController(parent);

            parent.HidesBottomBarWhenPushed = true;
        }

        public override void ViewDidLayoutSubviews()
        {
            base.ViewDidLayoutSubviews();

            if (View != null && View.Superview != null && View.Frame.Height != View.Superview.Frame.Height)
            {
                View.Frame = View.Superview.Frame;
                Element.Layout(View.Frame.ToRectangle());
            }
        }
    }
}

As usual it couldn't go flawless with Xamarin.Forms , and it turned out, that child view controller is not stretched to frame of a parent view controller. And that I had to manually fix inside ViewDidLayoutSubviews .

在此处输入图片说明

Also beware, that every new view controller pushed to the navigation stack after view controller with hidden tab bar wan't have tab bar either. So in my case every "detail" page is derived from the NoTabBarPage .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM