有感
....请允许我在文章开篇爆句粗口, 就这导航栏错乱这 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