【问题标题】:How to hide Tab Bar on push in Xamarin.Forms?如何在 Xamarin.Forms 中隐藏标签栏?
【发布时间】:2021-12-09 19:49:35
【问题描述】:

最近几天我在 iOS 上的 Xamarin.Forms 中使用 TabbedPage 苦苦挣扎。我找到了一些类似的解决方案: https://forums.xamarin.com/discussion/20901/hide-tab-bar-on-push

但是,它们都不能很好地工作。我还尝试将TabbedRenderer 子类化并将TabBar 高度设置为0。它可以工作,但是如果我在NavigationPage.Pushed 事件处理程序中隐藏TabBar,则会出现一些延迟,例如TableView 底部有空白区域。

如果我尝试覆盖 NavigationRenderer 并在 PushViewController/PopViewController 方法中隐藏/显示选项卡栏,它有时会失败。例如,如果我快速来回导航,则不会调用方法 PopViewController,NavigationStack 已损坏且 Tab Bar 未恢复。

我认为唯一好的解决方案是让这个属性工作:UIViewController.HidesBottomBarWhenPushed。但是,我不知道该怎么做,因为在渲染器中设置/覆盖它不起作用。

有人成功地显示和隐藏 TabBar 了吗?

【问题讨论】:

  • 嗨,您有什么问题吗?
  • 你解决了这个问题吗?

标签: ios xamarin xamarin.ios xamarin.forms uitabbar


【解决方案1】:

我设法实现了一个解决方案,该解决方案在隐藏TabBar 后解决了空格问题。您可以在this article 中阅读有关它的更多详细信息。

为了解决这个问题,我们只需要布局所有ChildViewControllers。这是我自定义TabbedPage 及其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();
            }
        }
    }
}  

最终结果:

【讨论】:

  • 对于 iPhone X,这是否不起作用。你能在 iPhone X 上使用它吗?
  • 让它在 iPhone X 上运行。您需要将 TabBarHeight 设置为 83:private void UpdateFrame(bool isHidden) { var tabFrame = this.TabBar.Frame; if (TabBar.Frame.Height > 50) TabBarHeight = 83; tabFrame.Height = isHidden ? 0 : TabBarHeight; this.TabBar.Frame = tabFrame; }
  • 这个解决方案对我有用。谢谢。为人整合此。 iOS中会有2个文件,一个是TabbedRenderer,一个是Page Renderer。
  • @Wojciech:你有安卓的解决方案吗?我正在使用 xamarin forms latest api 将 android 上的标签栏显示到底部,但找不到任何处理程序来隐藏标签栏。
  • @ArunGupta 不幸的是,我没有针对 android 的解决方案,但我认为它应该比在 iOS 上更容易。您可以尝试使用这些源并修改渲染器:github.com/thrive-now/BottomNavigationBarXF(我没有尝试过)
【解决方案2】:

有一种解决方案不需要任何渲染,并且适用于 Android 和 iOS。

TabbedPage 包装在NavigationPage 中,这样您的应用结构就变成了

  • 导航页(根)
    • 点击页面
      • 导航页
        • ContentPage(带标签栏)
    • ContentPage(无标签栏)

在 TabbedPage 上,您必须隐藏“根”NavigationPage 的导航栏,否则您有 2 个导航栏。

<TabbedPage
    ...
    HasNavigationBar="False"> 

如果你使用'root'NavigationPage推送页面,标签栏是隐藏的,底部没有空格。

请参阅我的示例: https://github.com/Jfcobuss/HideTabbarExample/tree/master/HideTabbarExample

【讨论】:

  • 您使用的是什么版本的表单?
  • 我尝试使用 Xamarin.Forms 3.0.0,它仍然有效。低版本还没试过
  • 我的错误,我在考虑不同的属性。我认为最新的 Xamarin.Forms 具有隐藏标签栏的属性。但是,您的解决方案会更改应用程序的行为,因为在您的情况下,导航不会发生在选项卡内,而是发生在外部,如果您想为每个选项卡保留不同的导航堆栈,则可能不会发生这种情况。此外,您还会失去一些内置功能,例如:后退按钮不会以前一个标签命名,向前导航会导致整个标签栏滑动。
  • 是的,你的权利。就我而言,我总是隐藏后退按钮标题,所以这对我来说不是问题。使用此解决方案,它将显示 TabbedPage 的标题。动画并不像应该的那样流畅。我希望 Xamarin 将努力使 HidesBottomBarWhenPushed 在没有渲染器和底部空白的情况下工作
  • @JopCobussen 你知道如何去除底部的空白吗?
【解决方案3】:

我尝试过的:

  • 创建 ContentPage 的子类并在其中创建 BindableProperty(如 HidesBottomBarWhenPushed)。我在PageRenderer中设置了ViewController.hidesBottomBarWhenPushed,但它不起作用,虽然我可以得到这个属性的值。

  • 在 PageRenderer 的初始构造函数中设置this.hidesBottomBarWhenPushed,仍然没有运气。

我认为hidesBottomBarWhenPushed 一定有问题,我们不能通过这种方式隐藏标签栏。作为一种临时且简单的解决方法,我在 PageRenderer 中更改了 TabBarController.TabBar 的可见性

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

它的行为就像你上面所说的那样,底部有一些延迟和空白。我禁用了 push 和 pop 的动画,问题就消失了。

测试:

【讨论】:

  • 抱歉回复晚了(我正在度假),感谢您的回答。我检查了您的解决方案,它隐藏了标签栏,但是即使我关闭动画,仍然有空白空间,并且我的 TableView 不会放大以填充空间。无论如何,我需要保留这些动画,所以不幸的是这对我来说不是一个解决方案:(
【解决方案4】:

我确实设法使用HidesBottomBarWhenPushed 属性以更“本机”的方式获得了预期的结果。如果你看一下导航是如何为 iOS 实现的,你会注意到,在将新的视图控制器推入导航堆栈 it gets wrapped in a parent view controller 内的 CreateViewControllerForPage 方法之前。它让我有机会拦截尚未推送到导航堆栈的已创建视图控制器并设置HidesBottomBarWhenPushed 属性。为此,我创建了一个从ContentPage 派生的空类:

public class NoTabBarPage : ContentPage { }

所以现在从NoTabBarPage 派生的每个页面都没有底部标签栏。

还有一个可以让一切正常工作的渲染器:

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

像往常一样,Xamarin.Forms 不能完美无缺,结果证明,子视图控制器没有拉伸到父视图控制器的框架。我必须在ViewDidLayoutSubviews 中手动修复。

还要注意,每个新的视图控制器在带有隐藏标签栏的视图控制器之后推送到导航堆栈也没有标签栏。所以在我的例子中,每个“详细信息”页面都来自NoTabBarPage

【讨论】:

    【解决方案5】:

    当我需要在屏幕上绘制标签栏之前隐藏标签栏时,我遇到了一个问题。

    Wojciech Kulik 的解决方案帮助了我,但它在导航到标签页时开始闪烁。

    下面的代码解决了我的问题。希望对您有所帮助。 把它放在你的 TabbedRenderer 派生类中

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多