问题所在
现在很多App为了追求美观、简洁,其中的一些页面都设计为导航栏隐藏的效果,在有导航栏和没导航栏的页面来回切换,就要在隐藏导航栏的页面做好导航栏的显示、隐藏的处理,而且还不知道跳转到下一个页面或返回上一个页面的时候到底会存在什么问题,下面讲一讲我们公司的处理方式(借鉴了一些想法,也增加了一些自己优化的点)。
看下Demo效果
录屏效果有限,欢迎下载Demo体验:https://github.com/ZhaoheMHz/CommonNavigationController
思路及具体实现
导航栏的处理,如果分散开到各个UIViewController中,则之后的管理和维护会变得比较麻烦,这里统一在UINavigationController中处理,所以我们创建一个UINavigationController的子类
我们要做的还有就是在导航栏在push或pop的时候,对即将出现的页面进行导航栏的隐藏或显示,这样处理有两个方法
1、将当前展示的页面设置为self.navigation.delegate,在代理方法
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
中,对导航栏进行隐藏或者显示的操作
2、在即将出现的页面的viewWillAppear和viewWillDisappear中处理导航栏
方法二经实践是不方便、不高效也容易出现问题的。而第一种方法需要在各个页面设置代理,也是比较麻烦的,所以我们统一在ZHNavigationController中进行代理方法的操作,我们把ZHNavigationController的delegate设置为自身,所有代理方法的处理都在ZHNavigationController中处理。
ZHNavigationViewController.m中:
- (instancetype)init {
if (self == [super init]) {
self.delegate = self;
}
return self;
}
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController {
if (self == [super initWithRootViewController:rootViewController]) {
self.delegate = self;
}
return self;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// 判断页面导航栏是否需要隐藏
BOOL isNavHidden = NO;
if ([viewController isKindOfClass:[NavHideViewController class]]) {
isNavHidden = YES;
}
[self setNavigationBarHidden:isNavHidden animated:YES];
}
前两个init方法,用于设置代理,然后最后的代理方法中处理我们的导航栏的隐藏和显示,在willShowViewController方法的if中,是我们需要隐藏导航栏的UIViewController,如果还想隐藏其他页面的导航栏,直接新增一个||判断条件就好了:
if ([viewController isKindOfClass:[NavHideViewController class]] || [viewController isKindOfClass:[另一个Controller class]]) {
isNavHidden = YES;
}
然后这里有一点需要注意的是,就是,如果一个UIViewController的导航栏隐藏了,则这个页面的滑动返回手势也失效了,所以我们要将这个手势加回来,同样是在ZHNavigationController中:
- (void)viewDidLoad {
[super viewDidLoad];
[self slideBack];
}
#pragma mark - 处理滑动返回
- (void)slideBack{
// 设置自身的滑动返回手势的代理为自身
self.interactivePopGestureRecognizer.delegate = self;
}
// 代理方法,在滑动手势即将开始的时候调用,返回YES则允许滑动返回,返回NO则滑动手势无效
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// 这里只在当前UINavigationController的栈中有两个以上的Controller的时候可以滑动返回,也就是如果当前显示的是RootViewController,则不允许滑动返回,加上这个处理,可以防止出现在RootViewController也在屏幕左侧滑动导致页面假死的情况出现
if (self.viewControllers.count >= 2) {
return YES;
}
return NO;
}
一点建议
如果有些页面需要做到导航栏透明,但上面的UIBarButtonItem还在的效果,或者有些页面要做导航栏随着页面的滚动而出现渐变的效果,这种情况建议还是做一个假的导航栏放在上面,否则对一个页面的导航栏的透明度做处理,与隐藏、显示一样,是非常容易在页面切换的时候出现问题。
下面提供一种比较方便的假导航栏的处理方式,这种方式可以继续按照导航栏的leftBarButtonItem、rightBarButtonItem、middleView的方式进行导航栏的配置,而不需要自己重新写布局。
假导航
思路主要是,再创建一个UINavigationController,然后给他指定一个新的RootViewController,将新的UINavigationController的navigationBar添加到当前页面的View上,当前页面将这两个Controller都设置为属性,所有导航的相关的操作,都在这两个属性上进行操作。
@interface NavFakeViewController ()
@property (nonatomic, strong) UINavigationController *fakeNav;
@property (nonatomic, strong) UIViewController *fakeSelf;
@end
@implementation NavFakeViewController
- (void)viewDidLoad {
[super viewDidLoad];
/** 创建假的导航、rootViewControler */
self.fakeSelf = [[UIViewController alloc] init];
self.fakeNav = [[UINavigationController alloc] initWithRootViewController:self.fakeSelf];
/** 将假的导航栏放到当前页面上 */
self.fakeNav.navigationBar.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 64);
[self.view addSubview:self.fakeNav.navigationBar];
/** 配置一些按钮 */
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
button.backgroundColor = [UIColor redColor];
// 这里设置导航按钮时,要设置给self.fakeSelf
self.fakeSelf.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];
// 而要拿到navigationBar去做一些透明处理,一定要用下面的方式取出
// self.fakeNav.navigationBar
}
@end