导航栏隐藏 && 导航栏错乱

有感

....请允许我在文章开篇爆句粗口, 就这导航栏错乱这 Bug, 我真日了狗了. 自从测试小伙伴发现这个问题以来, 已经有几天的时间了, 就复现这个 Bug, 就花费了大笔时间. 调研了半天才终于把这个 Bug 复现了. 写这篇文章的目的, 一个是记录一下项目中遇到的疑难杂症, 另一个就是希望能帮助到那些同样被这些问题困扰的小伙伴们. 总之, 这一段就是吐槽一下. 废了那么多话, 开始正题吧. 先放出来一个 Demo.

Demo

先给大家 Demo 的下载地址 Click Here
简单的讲解一下这个 Demo, KeyWindowRootViewController 是一个UITabBarViewController, UITabBarViewController 的每一个ViewController 都嵌套了一个MLNavigationController. 每一个ViewController 都继承自MLBaseViewController. 在MLBaseViewController.h 中, 声明了一个枚举类型MLNavigationHiddenType, 这个枚举类型来控制整个工程隐藏NavigationBar 的方式. MLBaseViewController.h 中还声明了一个全局变量hiddenType, 用这个变量来设置整个工程隐藏NavigationBar 的方式. 整个工程中, 只有MLUserHomePageViewController, MLMineViewControllerMLLoginViewControllerNavigationBar 处于隐藏状态. OK, 差不多介绍到这里吧, 细节部分大家具体看代码吧.

Demo 中使用的隐藏方式

  1. [self.navigationController setNavigationBarHidden: YES];
  2. [self.navigationController setNavigationBarHidden: YES animated: NO];
  3. [self.navigationController setNavigationBarHidden: YES animated: YES];
  4. [self.navigationController setNavigationBarHidden: YES animated: animated];
  5. self.navigationController.delegate = self 这种方法在最后为大家讲解, 这其实才是真正的究极方法

其实NavigationBar的显示与隐藏其实很简单, 只需要在ViewControllerviewWillAppear 中隐藏 NavigationBar, 在试图控制器的 viewWillDisappear 中显示 NavigationBar就可以了, 但是仅仅这么做, 会带来一些 UI 上的 Bug, 其实我们也可以定义一些成员变量来控制 Bug 的产生, 但是很繁琐, 项目庞大了之后, 会导致代码极其不易维护。代码如下:

- (void) viewWillAppear:(BOOL)animated {
  [super viewWillAppear: animated];

  // 方法1
  [self.navigationController setNavigationBarHidden: YES];

  // 方法2
  [self.navigationController setNavigationBarHidden: YES animated: NO];

  // 方法3
  [self.navigationController setNavigationBarHidden: YES animated: YES];

  // 方法4
  [self.navigationController setNavigationBarHidden: YES animated: animated];
}

- (void) viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear: animated];

  // 方法1
  [self.navigationController setNavigationBarHidden: NO];

  // 方法2
  [self.navigationController setNavigationBarHidden: NO animated: NO];

  // 方法3
  [self.navigationController setNavigationBarHidden: NO animated: YES];

  // 方法4
  [self.navigationController setNavigationBarHidden: NO animated: animated];
}

文中提到的 Bug

Bug1: 没有导航栏的试图控制器没有导航栏的试图控制器之间的切换效果.
Bug2: 没有导航栏的控制器有导航栏的控制器 之间进行切换的效果.
Bug3: 没有导航栏的控制器 Present 一个 视图控制器 的效果.
Bug4: NavigationBar 错乱.
Bug5: UITabBarController 切换ViewController 的效果.

几种隐藏导航栏方法产生的问题

方法1 [self.navigationController setNavigationBarHidden: YES];

方法2 [self.navigationController setNavigationBarHidden: YES animated: NO];

先来说说方法1方法2, 其实这两个方法效果是几乎一样的, 全都是隐藏NavigaionBar并且不需要使用动画. 我们先来看一下效果, 下面这张动图是 没有导航栏的控制器没有导航栏的控制器 之间进行切换的效果:


没有NavigationBar 的两个 ViewController 切换效果

其实这个效果还算可以, 也没有什么非常不友好的 UI 效果, 但是接下来的动图, 就会给用户带来一些很不愉快的体验: Bug2没有导航栏的控制器有导航栏的控制器 之间进行切换的效果:


没有NavigationBar 和 有NavigationBar 的 ViewController 切换效果


可以清楚的看到, 在使用Pop 返回手势的时候, 右上角有一个非常明显的黑色区域, 效果非常不尽如人意, 其实不友好的地方不仅仅只有这一个地方, 看下图: Bug3 没有导航栏的控制器 Present 一个 视图控制器 的效果:


没有NavigationBar 的 ViewController Present 一个 ViewController


由于我们在 viewWillDisappear 方法中, 做了显示NavigationBar 的操作, 所以当我们点击登录按钮的一瞬间, 导航栏出现了. 这会给细心的用户带来一种非常匪夷所思的感觉, 同样非常的不友好. 但是如果仅仅如此的话, 可能有些小伙伴也就忍了, 也就懒得继续钻研下去了, 但是... 重点来了, 使用方法1方法2隐藏NavigationBar, 会带来一个非常不可思议的 Bug, 也就是本文题目中写道的, NavigationBar错乱的 Bug, Bug4 请仔细看下面这个动图(这个动图时间有点长, 所以质量下降了, 主要观察NavigationBar部分就可以了):

导航栏隐藏 && 导航栏错乱_第1张图片
NavigationBar 错乱 Bug


我相信一定不止是我一个人遇到了这个问题, 在网上搜索了很多帖子, 确实也有不少的小伙伴遇到了这个问题, 我先来说一下这个问题的复现方法:

  1. 需要3个ViewController (至少3个, 更多也可以), 隐藏第一个ViewControllerNavigationBar(其实你隐藏哪个都可以, 我这里以第一个为例)
  2. push 进入ViewController2, 使用 iOS7后系统的右划返回手势, 这里注意一下, 手指右划到一半的时候, 取消Pop 动作, 此时页面依然停留在ViewController2当中, 然后再完整的做一次右划手势PopViewController1.
  3. 这个时候, 实际上这个NavigationContrllerNavigationBar 结构已经错乱了, 我们可以PushViewController3中去看一下NavigationBar 的情况. (无论是 PushViewContrller3 或者是 PushViewController4,ViewController5,ViewController6, 你会发现所有的ViewContrllerNavigationBar 都会非常神奇的一闪而过, 然后变成了 ViewController2 NavigationBar)

我只想说: WTF, 好神奇的样子. 其实这个 Bug 复现起来并不是很困难, 难点在于:
你的测试小伙伴会给你提 Bug 说:

ViewController3的导航栏有问题, 但是这个问题非常偶然, 我也没有办法复现.

WTF, 你不复现我怎么办, 难道真的要我把需求放下, 来花大笔的时间来复现这个 Bug 么? T_T...
不过还好, 这个 Bug 我这边已经帮助大家复现出来了, 我相信, 一定会帮助到一些已经在风中凌乱了的小伙伴们( OK, 装逼结束, 其实是在 Google 上搜索到了这个小伙伴的文章后受到了启发, 不过这篇文章中, 并没有给出一个非常 Prefect 的解决办法).
好了, 废话不多说了, 接下来看看到底该怎么解决上面这个 Bug , 以及上面提到的 UI 不友好的地方吧, 接下来, 我们来看方法3.

方法3 [self.navigationController setNavigationBarHidden: YES animated: YES];

使用方法3来设置NavigationBar 的隐藏和出现, 经过测试, 解决了 Bug2 没有导航栏的控制器有导航栏的控制器 之间进行切换的效果, 看下图:


解决的 Bug


方法3确实解决了 Bug2, 其实方法3也同样的解决了NavigationBar 错乱的问题, 经测试, 隐藏NavigaionBar 的时候, 将动画属性置为YES, 就能解决NavigationBar 错乱的问题了, 但是这样做也是会付出一些代价的.

  • 首先: Bug3 没有导航栏的控制器 Present 一个 视图控制器 的效果, 并没有得到解决. (大家可以自行查看 Demo)
  • 其次: 将动画属性设置为 YES 之后, 会带来两个新的问题, 我将这两个问题定义为 Bug1Bug5.
    先来看看 Bug1, 在方法1方法2中的第一个动图, 没有导航栏的试图控制器没有导航栏的试图控制器之间的切换效果, 当动画属性置为YES后, 原本效果还不错的地方, 也产生了不友好的用户体验, 如下图:

带来的新的 Bug


...什么鬼..., 这NavigationBar 干嘛呢.... 路过一下是么? -.-!!! 体验非常不好, 再看下一个, TabBarItem 之间的切换效果: Bug5 UITabBarController 试图切换效果:


UITabBarController 切换的效果


好家伙, 这玩意...不看不知道,一看吓一跳啊....每次进入我的页面的时候, 都要哆嗦一下, 这感觉太不爽了.

小结: 仅仅将动画属性置为 YES, 还是不太科学, 那再来看看方法4

方法4 [self.navigationController setNavigationBarHidden: YES animated: animated];

和刚才一样, 先来说说 方法4所解决的 Bug 吧.

  1. 方法3一样, 方法4同样解决了 Bug2Bug4.
  2. 方法4还解决了 Bug5
    与语言表达有点麻烦, 来看看下面的动图, 看看方法4所解决的 Bug:

Auto_FixBug.gif

再来看看方法4所未能解决的问题吧. Bug1Bug3:


Auto_Bugs.gif

其实方法4, 已经做的很好了, 解决了大部分的 Bug, 但是我们可以看到依然存在这两个不友好的 Bug. 接下来就给大家介绍这个究极方法.

究极方法 self.navigationController.delegate = self

先来说说写法吧, 最开始的时候, 我是写了一个继承自UINavigationController 的类, 设置代理并遵守协议, .m 文件中的3个关键方法如下:

#pragma mark - ViewController Life Circle
#pargma mark -
#Pargma mark ViewDidLoad
- (void) viewDidLoad {
  [super viewDidLoad];

  // 1. 设置代理
  self.delegate = self;
}

#pragma mark - Private Methods
#pragma mark -
#pragma mark Whether need Navigation Bar Hidden
- (BOOL) needHiddenBarInViewController:(UIViewController *)viewController {

    BOOL needHideNaivgaionBar = NO;

  // 在这里判断, 哪个 ViewController 需要隐藏导航栏, 如果有第三方的 ViewController 也需要隐藏 NavigationBar, 我们也需要在这里设置.
    if ([viewController isKindOfClass: [MLMineViewController class]] ||
        [viewController isKindOfClass: [MLUserHomePageViewController class]] ||
        [viewController isKindOfClass: [MLLoginViewController class]]) {
        needHideNaivgaionBar = YES;
    }

    return needHideNaivgaionBar;
}

#pragma mark - UINaivgationController Delegate
#pragma mark -
#pragma mark Will Show ViewController
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {

  // 在 NavigationController 的这个代理方法中, 设置导航栏的隐藏和显示
    [self setNavigationBarHidden: [self needHiddenBarInViewController: viewController]
                        animated: animated];
}

不否认, 这样的写法确实可行, 但是考虑到, 如果某一个第三方的ViewController 同样使用了这种方法来隐藏自己的NavigationBar, 那么就意味着将 NavigationController 的代理指向了其他的ViewController, 那么我们 App 中的所有隐藏NavigationBar 的逻辑就都失效了, 所以为了保险起见, 我将上述代码写到了MLBaseViewController 中, 并在MLBaseViewControllerViewWillAppear 方法中, 将delegate 指向了self. 代码如下:

@implementation MLBaseViewController

#pragma mark - ViewController Life Circle
#pragma mark -
#pragma mark ViewWillAppear
- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear: animated];

  // 1. 返回手势代理
  self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;

  // 2. 导航控制器代理
  self.navigationController.delegate = self;
}

#pragma mark - Private Methods
#pragma mark -
#pragma mark Whether need Navigation Bar Hidden
- (BOOL) needHiddenBarInViewController:(UIViewController *)viewController {

    BOOL needHideNaivgaionBar = NO;

    if ([viewController isKindOfClass: [MLMineViewController class]] ||
        [viewController isKindOfClass: [MLUserHomePageViewController class]] ||
        [viewController isKindOfClass: [MLLoginViewController class]]) {
        needHideNaivgaionBar = YES;
    }

    return needHideNaivgaionBar;
}

#pragma mark - UINaivgationController Delegate
#pragma mark -
#pragma mark Will Show ViewController
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {

    [self.navigationController setNavigationBarHidden: [self needHiddenBarInViewController: viewController]
                        animated: animated];
}

@end

这样就可以避免, 第三方的ViewControllerNavigationControllerdelegate 指向了一个我们所不了解的位置.

Note1: 子类尽量不要实现 - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated方法, 当然如果你的子类实现了这个方法, 别忘了调用 super 就好了.

Note2: 在ViewWillAppear 方法中, 我们看到了一句代码:self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;, 添加这句代码的原因在于: 当你自定义了NavigationBarBackButton 或 隐藏了NavigationBar之后, 系统的返回手势就失效了, 加上这句之后, 返回手势就 OK 了.

到这里, 其实我们已经完美的解决了上述的5个 Bug 了, 来看两张动图, 爽一下吧:


几个 UI 不友好的 Bug
导航栏隐藏 && 导航栏错乱_第2张图片
NavigationBar 错乱的 Bug

怎么样? 效果是不是还可以? 是不是棒棒哒? 这个写法在公司项目中亲测可行, 至今还未出现什么疑难杂症的 Bug, 小伙伴对文章有什么疑问 或 我哪里有些错的地方, 留言给我就好了.



文/CristianoRLong(简书作者)
原文链接:http://www.jianshu.com/p/e4448c24d900
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

你可能感兴趣的:(iOS)