导航栏隐藏 && 导航栏错乱
有感
....请允许我在文章开篇爆句粗口, 就这导航栏错乱这 Bug, 我真日了狗了. 自从测试小伙伴发现这个问题以来, 已经有几天的时间了, 就复现这个 Bug, 就花费了大笔时间. 调研了半天才终于把这个 Bug 复现了. 写这篇文章的目的, 一个是记录一下项目中遇到的疑难杂症, 另一个就是希望能帮助到那些同样被这些问题困扰的小伙伴们. 总之, 这一段就是吐槽一下. 废了那么多话, 开始正题吧. 先放出来一个 Demo.
Demo
先给大家 Demo 的下载地址 Click Here
简单的讲解一下这个 Demo:
-
KeyWindow
的RootViewController
是一个UITabBarViewController
-
UITabBarViewController
的每一个ViewController
都嵌套了一个MLNavigationController
. - 每一个
ViewController
都继承自MLBaseViewController
. - 在
MLBaseViewController.h
中, 声明了一个枚举类型MLNavigationHiddenType
, 这个枚举类型来控制整个工程隐藏NavigationBar
的方式. -
MLBaseViewController.h
中还声明了一个全局变量hiddenType
, 用这个变量来设置整个工程隐藏NavigationBar
的方式. - 整个工程中, 只有
MLUserHomePageViewController
,MLMineViewController
和MLLoginViewController
的NavigationBar
处于隐藏状态.
OK, 差不多介绍到这里吧, 细节部分大家具体看代码吧.
Demo 中使用的隐藏方式
[self.navigationController setNavigationBarHidden: YES];
[self.navigationController setNavigationBarHidden: YES animated: NO];
[self.navigationController setNavigationBarHidden: YES animated: YES];
[self.navigationController setNavigationBarHidden: YES animated: animated];
-
self.navigationController.delegate = self
这种方法在最后为大家讲解, 这其实才是真正的究极方法
其实NavigationBar
的显示与隐藏其实很简单, 只需要在ViewController
的viewWillAppear
中隐藏 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
__Bug__1: 没有导航栏的试图控制器
和 没有导航栏的试图控制器
之间的切换效果.
__Bug__2: 没有导航栏的控制器
和 有导航栏的控制器
之间进行切换的效果.
__Bug__3: 没有导航栏的控制器
Present 一个 视图控制器
的效果.
__Bug__4: NavigationBar
错乱.
__Bug__5: UITabBarController
切换ViewController
的效果.
几种隐藏导航栏方法产生的问题
方法1 [self.navigationController setNavigationBarHidden: YES];
方法2 [self.navigationController setNavigationBarHidden: YES animated: NO];
先来说说方法1
和方法2
, 其实这两个方法效果是几乎一样的, 全都是隐藏NavigaionBar
并且不需要使用动画. 我们先来看一下效果, 下面这张动图是 没有导航栏的控制器
和 没有导航栏的控制器
之间进行切换的效果:

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

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

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

我相信一定不止是我一个人遇到了这个问题, 在网上搜索了很多帖子, 确实也有不少的小伙伴遇到了这个问题, 我先来说一下这个问题的复现方法:
- 需要3个
ViewController
(至少3个, 更多也可以), 隐藏第一个ViewController
的NavigationBar
(其实你隐藏哪个都可以, 我这里以第一个为例) - 先
push
进入ViewController2
, 使用 iOS7后系统的右划返回手势, 这里注意一下, 手指右划到一半的时候, 取消Pop
动作, 此时页面依然停留在ViewController2
当中, 然后再完整的做一次右划手势Pop
回ViewController1
. - 这个时候, 实际上这个
NavigationContrller
的NavigationBar
结构已经错乱了, 我们可以Push
到ViewController3
中去看一下NavigationBar
的情况. (无论是Push
到ViewContrller3
或者是Push
到ViewController4
,ViewController5
,ViewController6
, 你会发现所有的ViewContrller
的NavigationBar
都会非常神奇的一闪而过, 然后变成了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 没有导航栏的控制器
和 有导航栏的控制器
之间进行切换的效果, 看下图:

方法3
确实解决了 Bug2, 其实方法3
也同样的解决了NavigationBar
错乱的问题, 经测试, 隐藏NavigaionBar
的时候, 将动画属性置为YES
, 就能解决NavigationBar
错乱的问题了, 但是这样做也是会付出一些代价的.
- 首先: Bug3
没有导航栏的控制器
Present 一个视图控制器
的效果, 并没有得到解决. (大家可以自行查看 Demo) - 其次: 将动画属性设置为
YES
之后, 会带来两个新的问题, 我将这两个问题定义为 Bug1 和 Bug5.
先来看看 Bug1, 在方法1
和方法2
中的第一个动图,没有导航栏的试图控制器
和没有导航栏的试图控制器
之间的切换效果, 当动画属性置为YES
后, 原本效果还不错的地方, 也产生了不友好的用户体验, 如下图:

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

好家伙, 这玩意...不看不知道,一看吓一跳啊....每次进入
我的
页面的时候, 都要哆嗦一下, 这感觉太不爽了.
小结: 仅仅将动画属性置为 YES
, 还是不太科学, 那再来看看方法4
方法4 [self.navigationController setNavigationBarHidden: YES animated: animated];
和刚才一样, 先来说说 方法4
所解决的 Bug 吧.
- 和
方法3
一样,方法4
同样解决了 Bug2 和 Bug4. -
方法4
还解决了 Bug5
与语言表达有点麻烦, 来看看下面的动图, 看看方法4
所解决的 Bug:

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

其实方法4
, 已经做的很好了, 解决了大部分的 Bug, 但是我们可以看到依然存在这两个不友好的 Bug. 接下来就给大家介绍这个究极方法.
究极方法 self.navigationController.delegate = self
先来说说写法吧, 最开始的时候, 我是写了一个继承自UINavigationController
的类, 设置代理并遵守<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
中, 并在MLBaseViewController
的ViewWillAppear
方法中, 将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
这样就可以避免, 第三方的ViewController
将NavigationController
的delegate
指向了一个我们所不了解的位置.
Note1: 子类尽量不要实现 - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
方法, 当然如果你的子类实现了这个方法, 别忘了调用 super
就好了.
Note2: 在ViewWillAppear
方法中, 我们看到了一句代码:self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;
, 添加这句代码的原因在于: 当你自定义了NavigationBar
的BackButton
或 隐藏了NavigationBar
之后, 系统的返回手势就失效了, 加上这句之后, 返回手势就 OK 了. 但是这句代码这样写仍然是有潜在问题的, 这会使得用户在使用 App 不当时, App 会产生一种假死的现象, 请看 PopGestureRecognizer Tips.
到这里, 其实我们已经完美的解决了上述的5个 Bug 了, 来看两张动图, 爽一下吧:


怎么样? 效果是不是还可以? 是不是棒棒哒?
补充内容__20160830
1、一种比较常用的导航栏隐藏
和 显示
的方法
还有一种比较常用的NavigationBar
隐藏和显示的方法. 只在ViewController
的基类里的ViewWillAppear
方法中设置NavigationBar
是否隐藏, 而ViewWillDisappear
方法中不设置导航栏是否隐藏. 这样也可以避免一些 UI
上不和谐的问题. Note: 如果你遇到了一些切换ViewController
不和谐的效果, 也可以试试这种方法. 代码如下(纯手写代码, 如有拼写错误请见谅):
@implementation MLBaseViewController
#pragma mark - ViewController Life Circle
#pragma mark -
#pragma mark ViewWillAppear
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear: animated];
// 1. 设置 NavigaionBar 的显示与隐藏, 这种方法只写在这个基类里就可以了, 至于哪些需要隐藏, 哪些不需要隐藏, 修改 -(void) needHideNavigationBar; 方法就 OK 了.
[self setNavigationBarHidden: [self needHiddenBarInViewController: viewController]
animated: animated];
}
#pragma mark - Private Methods
#pragma mark -
#pragma mark Whether need hide navigation bar
- (BOOL) needHideNavigationBar {
BOOL result = NO;
if ([self isKindOfClass: [MLMineViewController class]] ||
[self isKindOfClass: [MLUserHomePageViewController class]]) {
result = YES;
}
return result;
}
@end
2、再谈导航栏错乱的 Bug
上文中提到的NavigationBar
错乱Bug
的解决办法, 经过深度测试发现, 当 两个隐藏 NavigationBar
的试图进行切换的时候,仍然存在. 由于项目时间紧迫, 我这里的解决办法是将其中一个页面的 NavigationBar
从原来的CustomNavigationBar
改为了系统的NavigationBar
. 问题得到了暂时的解决. 希望对这个问题有研究的朋友可以一起探讨一下. 这个问题在项目不忙的时候, 我也会持续跟进.
Lemon龙说:
如果您在文章中看到了错误 或 误导大家的地方, 请您帮我指出, 我会尽快更改
如果您有什么疑问或者不懂的地方, 请留言给我, 我会尽快回复您
如果您觉得本文对您有所帮助, 您的喜欢是对我最大的鼓励
如果您有好的文章, 可以投稿给我, 让更多的 iOS Developer 在简书这个平台能够更快速的成长
上一篇: 🏠
下一篇: PopGestureRecognizer Tips