`hidesBottomBarWhenPushed = NO` 不起作用?

31

我在我的应用程序中有一个 UITabBar,我想在第一个选项卡的第一个 UIViewController 上隐藏它,通过在 AppDelegate 中加入以下代码进行实现:

// ... in MyAppDelegate.m
firstViewController.hidesBottomBarWhenPushed = YES;

firstViewController中,用户可以推动一个UIButton来推出一个新的UIViewController并保持在同一标签栏中。我希望在这种情况下UITabBar再次可见。我正在尝试像这样使它重新出现:

//... in firstViewController.m

secondViewController = [[SecondViewController alloc] init];
secondViewController.hidesBottomBarWhenPushed = NO;
[[self navigationController] pushViewController:secondViewController animated:YES];

不幸的是,不会使 UITabBar 重新出现。它仍然处于隐藏状态。

我该如何正确地将 UITabBar 重新显示出来?

先谢谢您。

9个回答

85

这是一个困扰了我一段时间的问题,直到最近我才找到了解决方法。属性hidesBottomBarWhenPushed非常奇怪,在我的看法中,它的工作方式是反直觉的。

问题在于,当您推送一个新的视图控制器(或弹出)时,导航控制器将询问所有视图控制器(从顶部到底部),是否想要隐藏底部栏,如果任何一个视图控制器回答YES,则选项卡栏将被隐藏,这就是为什么尽管在新视图控制器中设置为NO,但选项卡栏仍然保持隐藏状态。

这里是我的解决方案 - 在您希望没有选项卡栏的视图控制器中覆盖hidesBottomBarWhenPushed getter,并检查其是否位于堆栈顶部:

Objective-C

- (BOOL) hidesBottomBarWhenPushed
{
    return (self.navigationController.topViewController == self);
}

Swift (not so obvious, hence snippet)

override var hidesBottomBarWhenPushed: Bool {
    get {
        return navigationController?.topViewController == self
    }
    set {
        super.hidesBottomBarWhenPushed = newValue
    }
}

这样做能很好地将隐藏/显示逻辑封装在一个位置,因此您无需在执行隐藏操作的视图控制器之外考虑它。

1
很好的答案!我认为它给出了每个人所期望的结果。每个视图控制器都应该被允许选择是否需要底部栏。 - deadbeef
2
非常感谢,你让我的一天变得美好了,这个问题让我疯狂了 :) 这太蠢了,它应该直接能够正常工作,至少大家都期望它是这样的行为。 - sachadso
2
这太棒了,而且非常容易。我认为苹果应该将此行为作为一个选项添加进去。 - NKorotkov
这个解决方案实际上对原问题确实有效!但是,在我的情况下,它会在屏幕的下部创建一些不必要的动画问题(它会一直停留在屏幕上,直到推送动画完成)。我尝试了一个自定义的推送动画,结果导致选项卡栏没有出现动画效果。 - jxd
如果有人感兴趣的话:我通过在viewWillDisappear中使用自定义动画将导致问题的视图隐藏(alpha 0),并在viewWillAppear上重新显示来解决了我上面提到的问题。 - jxd

32
这是`hidesBottomBarWhenPushed`的文档说明(已加粗):
如果设置为YES,则底部栏会保持隐藏状态直到视图控制器从堆栈中弹出。 因此,你看到的行为似乎正是文档所描述的。首先向堆栈推入一个具有`hidesBottomBarWhenPushed=YES`属性的视图控制器。在那时,将其他视图控制器推入堆栈不会改变底部栏的隐藏状态。只要第一个视图控制器在堆栈中,底部栏将一直保持隐藏。
因此,我认为你需要想出另一种实现你的UI目标的方法。其中一种选项是将第一个视图控制器呈现为模态视图控制器,覆盖在选项卡栏控制器的视图之上。然后,当你想要进入第二个视图控制器时,只需解除第一个视图控制器,就可以实现这个目标。唯一的视觉差异将是过渡动画。
当然还有其他选择,但这是我首先想到的。
祝你好运!

1
啊,这就解释了,谢谢。我真希望它是“直到一个新的视图控制器被推入或从堆栈中弹出”,但是你能做什么呢?我现在正在寻找替代方案。 - rottendevice
是的,很遗憾。试试使用模态视图控制器吧。祝你好运! - CharlieMezak

16

我曾遇到同样的问题,但三个小时后我找到了解决方案! 在这个主题回答于2010年10月8日,Dave Batton说:

hidesBottomBarWhenPushed属性的正确使用方式是:

self.anotherViewController.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:self.anotherViewController animated:animated];

3

不确定是否已经找到了解决方案,但我刚刚成功地将其搞定了。

我的情况是:

我有一个带有4个选项卡的UITabBarController。 在其中一个选项卡上,它会加载一个带有按钮的UIViewController。这些按钮调用一个IBOutlet函数,该函数加载另一个包含底部选项卡的UIViewController

经过多次尝试和错误……

IBOutlet函数中,我执行以下操作:

{
 self.hidesBottomBarWhenPushed = YES;
 /* Push the new controller with tab bar */
}

这在使用UITabBarController时正常运行,标签栏向左滑动,同时从推送的控制器中滑出该标签栏。
显然,从功能角度来看,"返回"时需要将初始的UITabBarController标签栏推回去。
经过多次尝试和错误......
我在UIViewController中有一个viewWillDisappear方法,用于推送带有选项卡栏的UIViewController
- (void) viewWillDisappear:(BOOL)animated
{
    self.hidesBottomBarWhenPushed = NO;
}

我在模拟器上进行了一些快速测试,似乎可以正常工作。

一些贡献者认为这是不好的用户界面设计,但我目前正在尝试它,看看效果如何。

非常乐意接收任何反馈。 :)


1

在设置视图控制器中

我不需要在选项卡栏中设置屏幕,可以通过过去的两种方法来设置屏幕,并将屏幕设置为在显示底部选项卡栏中推送任何屏幕。

override func viewWillAppear(_ animated: Bool) {

     self.hidesBottomBarWhenPushed = true
}

override func viewDidAppear(_ animated: Bool) {

    self.hidesBottomBarWhenPushed = false
}

感谢您,如果有任何问题,请随时提问。

1
我想到了一个非常简单的方法来解决这个问题,就是通过子类化UINavigationController。你也可以使用带有关联对象的类别来实现相同的功能,但我已经有了一个子类,所以我只需把代码放在那里即可。
首先,在你的UINavigationController中添加一个实例变量:
@interface CustomNavigationController ()
{
    NSMutableSet *_viewControllersWithHiddenBottomBar;
}

@end

那么我重写了push和pop方法,接管了隐藏逻辑的处理:

- (void) pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if(viewController.hidesBottomBarWhenPushed)
    {
        viewController.hidesBottomBarWhenPushed = NO;
        [_viewControllersWithHiddenBottomBar addObject:viewController];
        [self rootViewController].hidesBottomBarWhenPushed = YES;
    }
    else
    {
        [self rootViewController].hidesBottomBarWhenPushed = NO;
    }
    [super pushViewController:viewController animated:animated];
}

- (UIViewController *) popViewControllerAnimated:(BOOL)animated
{
    if([_viewControllersWithHiddenBottomBar containsObject:self.viewControllers[self.viewControllers.count - 2]])
    {
        [self rootViewController].hidesBottomBarWhenPushed = YES;
    }
    else
    {
        [self rootViewController].hidesBottomBarWhenPushed = NO;
    }
    UIViewController *poppedViewController = [super popViewControllerAnimated:animated];
    [_viewControllersWithHiddenBottomBar removeObject:poppedViewController];
    return poppedViewController;
}

- (UIViewController *) rootViewController
{
    return ((UIViewController *)self.viewControllers.firstObject);
}

我使用hidesButtomBarWhenPushed属性来填充集合,然后在视图控制器级别重置该值(因为如果任何视图控制器设置了此属性,则其上方的所有内容也将隐藏)。为了简化事情,我使用根视图控制器来控制基于集合中的值显示和隐藏选项卡栏。
您还需要在某个地方初始化该集合,我只是使用了initWithRootViewController:
对我而言,这非常流畅,并且是我能想到的最不hacky的方法,而不会接管任何现有的动画并处理边缘情况。

1

我认为你误解了hidesBottomBarWhenPushed的用法。如果是YES,则底部栏将保持隐藏,直到视图控制器从堆栈中弹出。

因此,如果我正确理解了你的问题:

secondViewController应该是YES,firstViewController应该是NO。


1
我认为我实际上已经搞定了。我不想在第一个选项卡的第一个视图控制器上显示选项卡栏。然而,我希望它出现在第一个选项卡的第二个视图控制器上。我知道这很奇怪,但在应用程序的上下文中是有意义的。 - rottendevice

0

使用

secondViewController.hidesBottomBarWhenPushed = NO;

当插入一些操作时

firstViewController.hidesBottomBarWhenPushed = YES;

0

这个对我有用。

多亏了这里某个其他线程中的提示,我找到了一个解决方案,可以仅针对一个视图控制器隐藏选项卡栏,并为从其中调用的任何视图控制器重新建立它。

这样做,我可以保持常规的导航控制器链。

这就是我最终得到的:

#define kTabBarHeight               49 // This may be different on retina screens. Frankly, I have not yet tried.

- (void) hideTabBar:(BOOL)hide {

    // fetch the app delegate
    AppDelegate         *delegate   = [[UIApplication sharedApplication] delegate];

    // get the device coordinates
    CGRect              bounds      = [UIScreen mainScreen].bounds;
    float               width;
    float               height;

    // Apparently the tab bar controller's view works with device coordinates  
    // and not with normal view/sub view coordinates
    // Therefore the following statement works for all orientations. 
    width                   = bounds.size.width;
    height                  = bounds.size.height;

    if (hide) {

        // The tab bar should be hidden too. 
        // Otherwise it may flickr up a moment upon rotation or 
        // upon return from detail view controllers. 
        [self.tabBarController.tabBar setHidden:YES];

        // Hiding alone is not sufficient. Hiding alone would leave us with an unusable black
        // bar on the bottom of the size of the tab bar. 
        // We need to enlarge the tab bar controller's view by the height of the tab bar. 
        // Doing so the tab bar, although hidden, appears just beneath the screen. 
        // As the tab bar controller's view works in device coordinations, we need to enlarge 
        // it by the tab bar height in the appropriate direction (height in portrait and width in landscape)
        // and in reverse/upside down orientation we need to shift the area's origin beyond zero. 
        switch (delegate.tabBarController.interfaceOrientation) {
            case UIInterfaceOrientationPortrait:
                // Easy going. Just add the space on the bottom.
                [self.tabBarController.view setFrame:CGRectMake(0,0,width,height+kTabBarHeight)];
                break;

            case UIInterfaceOrientationPortraitUpsideDown:
                // The bottom is now up! Add the appropriate space and shift the rect's origin to y = -49
                [self.tabBarController.view setFrame:CGRectMake(0,-kTabBarHeight,width,height+kTabBarHeight)];
                break;

            case UIInterfaceOrientationLandscapeLeft:
                // Same as Portrait but add the space to the with but the height
                [self.tabBarController.view setFrame:CGRectMake(0,0,width+kTabBarHeight,height)];
                break;

            case UIInterfaceOrientationLandscapeRight:
                // Similar to Upside Down: Add the space and shift the rect. Just use x and with this time
                [self.tabBarController.view setFrame:CGRectMake(0-kTabBarHeight,0,width+kTabBarHeight,height)];
                break;

            default:
                break;
        }
    } else {
        // reset everything to its original state. 
        [self.tabBarController.view setFrame:CGRectMake(0,0,width,height)];
        [self.tabBarController.tabBar setHidden:NO];
    }

    return; 
}


- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{

    // It is important to call this method at all and to call it here and not in willRotateToInterfaceOrientation
    // Otherwise the tab bar will re-appear. 
    [self hideTabBar:YES];

    // You may want to re-arrange any other views according to the new orientation
    // You could, of course, utilize willRotateToInterfaceOrientation instead for your subViews. 
}

- (void)viewWillAppear: (BOOL)animated { 

    // In my app I want to hide the status bar and navigation bar too. 
    // You may not want to do that. If so then skip the next two lines. 
    self.navigationController.navigationBar.barStyle = UIBarStyleBlackTranslucent;
    [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];

    [self hideTabBar: YES];

    // You may want to re-arrange your subviews here. 
    // Orientation may have changed while detail view controllers were visible. 
    // This method is called upon return from pushed and pulled view controllers.   

    return;
}

- (void)viewWillDisappear: (BOOL)animated {     

    // This method is called while this view controller is pulled
    // or when a sub view controller is pushed and becomes visible
    // Therefore the original settings for the tab bar, navigation bar and status bar need to be re-instated

    [self hideTabBar:NO];

    // If you did not change the appearance of the navigation and status bar in viewWillAppear,
    // then you can skip the next two statements too. 
    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];

    return;
}

内联注释应解释每个语句的原因。然而,可能有更聪明的编码方式。

与隐藏状态栏和导航栏一起使用时,存在一个副作用,我不想向大家隐藏。 1.当从此导航控制器返回到调用导航控制器时,调用控制器上的状态栏和导航栏重叠,直到设备旋转一次或在前面的另一个选项卡之后再选择相关选项卡。 2.当调用视图控制器是表视图并且设备处于横屏模式时返回到该表格时,则表格以适合横屏的方向显示,但它的布局就好像它是纵向的。左上角很好,但一些表格单元格加上选项卡被隐藏在屏幕下方。右侧有一些空余空间。这也可以通过再次旋转设备来修复。

如果我找到这些小但令人讨厌的错误的解决方案,我会让您及时知道。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接