在我们日常开发中,总有那么一两个界面需要去隐藏导航栏,这时如何去合理的处理呢?笔者这里提供了几个常用的方案和带来的问题,并在最后给个笔者认为较为优雅的方法。
方案一:使用setNavigationBarHidden:animated:
方法直接处理
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:true animated:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.navigationController setNavigationBarHidden:false animated:animated];
}
这个使我们解决隐藏导航栏首先会想到的方案,这种方式虽然很好的解决了,首页隐藏导航栏,push
到新界面不隐藏的场景。但如下场景会有个过度动画,很难看:
-
- 首页需要隐藏,
push
的新界面也需要隐藏,这时就会有个隐藏--显示--隐藏的过度动画;
- 首页需要隐藏,
-
- 首页隐藏,然后在切换
tabBar
再回来,这时有一个导航栏向上消失的动画;
- 首页隐藏,然后在切换
所以这种直接使用的方案,不完美,pass。
方案二:使用UINavigationControllerDelegate
代理方法直接处理
@interface HomePageController ()
@end
@implementation HomePageController
#pragma mark - lifeCycle
- (void)viewDidLoad {
[super viewDidLoad];
// 设置导航控制器的代理为self
self.navigationController.delegate = self;
}
#pragma mark - < UINavigationControllerDelegate >
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// 判断要显示的控制器是否是自己
BOOL isShowHomePage = [viewController isKindOfClass:[self class]];
[self.navigationController setNavigationBarHidden:isShowHomePage animated:YES];
}
- (void)dealloc {
self.navigationController.delegate = nil;
}
通过当前self
对象来管理导航栏的显示和隐藏,虽然能解决切换tabBar
的动画问题,但是还是没有解决上面的问题1。
不完美,pass。
方案三:参考FDFullscreenPopGesture
思路的实现方案,完美解决以上问题
主要的思路是使用runtime
去hook
UIViewController
的viewWillAppear:
方法和navigationController
的pushViewController:animated:
及setViewControllers:animated:
方法实现。
相当于是在每个UIViewController
控制器,在调用viewWillAppear:
方法的时候,都去判断下是否需要隐藏导航栏setNavigationBarHidden:animated:
方法,这样的好处是,不用再当前控制器的viewWillDisappear:
中写显示方法,也就没有了过渡的动画问题,完美解决。
- 给
UIViewController
添加一个设置隐藏导航栏的属性lsl_prefersNavigationBarHidden
:
// MARK: - 给UIViewController添加lsl_prefersNavigationBarHidden属性
@implementation UIViewController (HandlerNavigationBar)
- (BOOL)lsl_prefersNavigationBarHidden
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setLsl_prefersNavigationBarHidden:(BOOL)hidden
{
objc_setAssociatedObject(self, @selector(lsl_prefersNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
-
hook
UIViewController
的viewWillAppear:
方法,并在此方法中执行已经存好的代码块:
typedef void(^_LSLViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);
@interface UIViewController (HandlerNavigationBarPrivate)
@property(nonatomic, copy) _LSLViewControllerWillAppearInjectBlock lsl_willAppearInjectBlock;
@end
// MARK: - 替换UIViewController的viewWillAppear方法,在此方法中,执行设置导航栏隐藏和显示的代码块。
@implementation UIViewController (HandlerNavigationBarPrivate)
+ (void)load
{
Method orginalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
Method swizzledMethod = class_getInstanceMethod(self, @selector(lsl_viewWillAppear:));
method_exchangeImplementations(orginalMethod, swizzledMethod);
}
- (void)lsl_viewWillAppear:(BOOL)animated
{
[self lsl_viewWillAppear:animated];
if (self.lsl_willAppearInjectBlock) {
self.lsl_willAppearInjectBlock(self, animated);
}
}
- (_LSLViewControllerWillAppearInjectBlock)lsl_willAppearInjectBlock
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setLsl_willAppearInjectBlock:(_LSLViewControllerWillAppearInjectBlock)block
{
objc_setAssociatedObject(self, @selector(lsl_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
-
hook
住navigationController
的pushViewController:animated:
及setViewControllers:animated:
方法,当控制器被压入栈中的时候,预存设置隐藏和显示导航栏的代码块到即将显示的控制器中,备控制器调用:
// MARK: - 替换UINavigationController的pushViewController:animated:方法,在此方法中去设置导航栏的隐藏和显示
@implementation UINavigationController (NavigationBar)
+ (void)load
{
Method originMethod = class_getInstanceMethod(self, @selector(pushViewController:animated:));
Method swizzedMethod = class_getInstanceMethod(self, @selector(lsl_pushViewController:animated:));
method_exchangeImplementations(originMethod, swizzedMethod);
Method originSetViewControllersMethod = class_getInstanceMethod(self, @selector(setViewControllers:animated:));
Method swizzedSetViewControllersMethod = class_getInstanceMethod(self, @selector(lsl_setViewControllers:animated:));
method_exchangeImplementations(originSetViewControllersMethod, swizzedSetViewControllersMethod);
}
- (void)lsl_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// Handle perferred navigation bar appearance.
[self lsl_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
// Forward to primary implementation.
[self lsl_pushViewController:viewController animated:animated];
}
- (void)lsl_setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated
{
// Handle perferred navigation bar appearance.
for (UIViewController *viewController in viewControllers) {
[self lsl_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
}
// Forward to primary implementation.
[self lsl_setViewControllers:viewControllers animated:animated];
}
- (void)lsl_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController
{
if (!self.lsl_viewControllerBasedNavigationBarAppearanceEnabled) {
return;
}
// 即将被调用的代码块
__weak typeof(self) weakSelf = self;
_LSLViewControllerWillAppearInjectBlock block = ^(UIViewController *viewController, BOOL animated){
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf setNavigationBarHidden:viewController.lsl_prefersNavigationBarHidden animated:animated];
}
};
// 给即将显示的控制器,注入代码块
appearingViewController.lsl_willAppearInjectBlock = block;
// 因为不是所有的都是通过push的方式,把控制器压入stack中,也可能是"-setViewControllers:"的方式,所以需要对栈顶控制器做下判断并赋值。
UIViewController *disappearingViewController = self.viewControllers.lastObject;
if (disappearingViewController && !disappearingViewController.lsl_willAppearInjectBlock) {
disappearingViewController.lsl_willAppearInjectBlock = block;
}
}
- (BOOL)lsl_viewControllerBasedNavigationBarAppearanceEnabled
{
NSNumber *number = objc_getAssociatedObject(self, _cmd);
if (number) {
return number.boolValue;
}
self.lsl_viewControllerBasedNavigationBarAppearanceEnabled = YES;
return YES;
}
- (void)setLsl_viewControllerBasedNavigationBarAppearanceEnabled:(BOOL)enabled
{
SEL key = @selector(lsl_viewControllerBasedNavigationBarAppearanceEnabled);
objc_setAssociatedObject(self, key, @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
方案三可以完美解决以上所遇见的问题,demo
中有相应的案例和完整代码。