....请允许我在文章开篇爆句粗口, 就这导航栏错乱这 Bug, 我真日了狗了. 自从测试小伙伴发现这个问题以来, 已经有几天的时间了, 就复现这个 Bug, 就花费了大笔时间. 调研了半天才终于把这个 Bug 复现了. 写这篇文章的目的, 一个是记录一下项目中遇到的疑难杂症, 另一个就是希望能帮助到那些同样被这些问题困扰的小伙伴们. 总之, 这一段就是吐槽一下. 废了那么多话, 开始正题吧. 先放出来一个 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, 差不多介绍到这里吧, 细节部分大家具体看代码吧.
[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];
}
Bug1: 没有导航栏的试图控制器
和 没有导航栏的试图控制器
之间的切换效果.
Bug2: 没有导航栏的控制器
和 有导航栏的控制器
之间进行切换的效果.
Bug3: 没有导航栏的控制器
Present 一个 视图控制器
的效果.
Bug4: NavigationBar
错乱.
Bug5: UITabBarController
切换ViewController
的效果.
[self.navigationController setNavigationBarHidden: YES];
[self.navigationController setNavigationBarHidden: YES animated: NO];
先来说说方法1
和方法2
, 其实这两个方法效果是几乎一样的, 全都是隐藏NavigaionBar
并且不需要使用动画. 我们先来看一下效果, 下面这张动图是 没有导航栏的控制器
和 没有导航栏的控制器
之间进行切换的效果:
其实这个效果还算可以, 也没有什么非常不友好的 UI 效果, 但是接下来的动图, 就会给用户带来一些很不愉快的体验: Bug2没有导航栏的控制器
和 有导航栏的控制器
之间进行切换的效果:
可以清楚的看到, 在使用Pop
返回手势的时候, 右上角有一个非常明显的黑色区域, 效果非常不尽如人意, 其实不友好的地方不仅仅只有这一个地方, 看下图: Bug3 没有导航栏的控制器
Present 一个 视图控制器
的效果:
由于我们在 viewWillDisappear
方法中, 做了显示NavigationBar
的操作, 所以当我们点击登录按钮的一瞬间, 导航栏出现了. 这会给细心的用户带来一种非常匪夷所思的感觉, 同样非常的不友好. 但是如果仅仅如此的话, 可能有些小伙伴也就忍了, 也就懒得继续钻研下去了, 但是... 重点来了, 使用方法1
和 方法2
隐藏NavigationBar
, 会带来一个非常不可思议的 Bug, 也就是本文题目中写道的, NavigationBar
错乱的 Bug, Bug4 请仔细看下面这个动图(这个动图时间有点长, 所以质量下降了, 主要观察NavigationBar
部分就可以了):
我相信一定不止是我一个人遇到了这个问题, 在网上搜索了很多帖子, 确实也有不少的小伙伴遇到了这个问题, 我先来说一下这个问题的复现方法:
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
.
[self.navigationController setNavigationBarHidden: YES animated: YES];
使用方法3
来设置NavigationBar
的隐藏和出现, 经过测试, 解决了 Bug2 没有导航栏的控制器
和 有导航栏的控制器
之间进行切换的效果, 看下图:
方法3
确实解决了 Bug2, 其实方法3
也同样的解决了NavigationBar
错乱的问题, 经测试, 隐藏NavigaionBar
的时候, 将动画属性置为YES
, 就能解决NavigationBar
错乱的问题了, 但是这样做也是会付出一些代价的.
没有导航栏的控制器
Present 一个 视图控制器
的效果, 并没有得到解决. (大家可以自行查看 Demo)YES
之后, 会带来两个新的问题, 我将这两个问题定义为 Bug1 和 Bug5.方法1
和方法2
中的第一个动图, 没有导航栏的试图控制器
和 没有导航栏的试图控制器
之间的切换效果, 当动画属性置为YES
后, 原本效果还不错的地方, 也产生了不友好的用户体验, 如下图:
...什么鬼..., 这NavigationBar
干嘛呢.... 路过一下是么? -.-!!! 体验非常不好, 再看下一个, TabBarItem
之间的切换效果: Bug5 UITabBarController
试图切换效果:
好家伙, 这玩意...不看不知道,一看吓一跳啊....每次进入我的
页面的时候, 都要哆嗦一下, 这感觉太不爽了.
小结: 仅仅将动画属性置为 YES
, 还是不太科学, 那再来看看方法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
的类, 设置代理并遵守
协议, .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 了.
到这里, 其实我们已经完美的解决了上述的5个 Bug 了, 来看两张动图, 爽一下吧:
怎么样? 效果是不是还可以? 是不是棒棒哒? 这个写法在公司项目中亲测可行, 至今还未出现什么疑难杂症的 Bug, 小伙伴对文章有什么疑问 或 我哪里有些错的地方, 留言给我就好了.