一. 前言
我们产品需求
中很经常会碰到某个界面
是需要隐藏导航栏
或者自定义导航栏
,但是跳转到下个界面
又需要显示导航栏
,更有甚者,比如说当前界面
是需要隐藏导航栏
的,这个界面
可以跳转到其他十来个界面
,其中有一半的界面
是需要隐藏导航栏
,一半
是需要显示导航栏
的,这样导航栏处理
起来就很麻烦,而且导航栏处理
的代码散落
在各个控制器界面
,后期寻找和修改起来也麻烦。
举个🌰 :
- 当前
发现界面
是隐藏导航栏
, - 同时
发现界面
可以跳转到店铺界面
和我的界面
-
店铺界面
是显示导航栏
,而我的界面
是隐藏导航栏
代码展示:
#import "FJShopViewController.h"
#import "FJProfileViewController.h"
#import "FJDiscoverViewController.h"
@interface FJDiscoverViewController ()
@end
@implementation FJDiscoverViewController
#pragma mark -------------------------- Life Circle
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = @"发现";
self.edgesForExtendedLayout = UIRectEdgeNone;
self.view.backgroundColor = [UIColor whiteColor];
self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;
UIButton *firstBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 100)];
[firstBtn setTitle:@"进入店铺界面" forState:UIControlStateNormal];
[firstBtn addTarget:self action:@selector(firstButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
firstBtn.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256.0)/256.0f green:arc4random_uniform(256)/256.0f blue:arc4random_uniform(256)/256.0f alpha:1.0f];
[self.view addSubview:firstBtn];
UIButton *secondBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 250, 200, 100)];
[secondBtn setTitle:@"进入我的界面" forState:UIControlStateNormal];
[secondBtn addTarget:self action:@selector(secondButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
secondBtn.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256.0)/256.0f green:arc4random_uniform(256)/256.0f blue:arc4random_uniform(256)/256.0f alpha:1.0f];
[self.view addSubview:secondBtn];
}
- (void)viewWillAppear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:YES animated:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:NO animated:animated];
}
#pragma mark -------------------------- Response Event
- (void)firstButtonClicked:(UIButton *)sender {
FJShopViewController *tmpVc = [[FJShopViewController alloc] init];
tmpVc.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:tmpVc animated:YES];
}
- (void)secondButtonClicked:(UIButton *)sender {
FJProfileViewController *tmpVc = [[FJProfileViewController alloc] init];
tmpVc.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:tmpVc animated:YES];
}
效果展示:
我们可以看到:尽管我们在发现界面
的viewWillAppear
和viewWillDisappear
做了如下处理
- (void)viewWillAppear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:YES animated:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:NO animated:animated];
}
但是跳转到我的界面
这时导航栏
显示还是有问题,尤其是手势滑动返回
的时候,还是显得不协调。
这种情况处理起来就相对比较麻烦,因为我的界面
在viewWillAppear
和viewWillDisappear
也做了处理,这就需要发现界面
和我的界面
的两者配合
起来处理才能达到协调
的目的。
但是通过FJFNavigationBarManager
,只需要简单的设置需要隐藏的界面
,其他的都不需要管,同时如果在debug模式
下,如果你在某个界面通过函数[self.navigationController setNavigationBarHidden:YES animated:animated];
来隐藏导航栏
,会通过崩溃
来提醒你要去导航栏管理器
里面设置才可以。
#import "FJFViewControllerConfigureManager.h"
@implementation FJFViewControllerConfigureManager
/************* ⚡️⚡️⚡️ 此处为需要隐藏导航栏的控制器 ⚡️⚡️⚡️ *************/
+ (NSArray<NSString *> *)vcNeedsNavBarHiddenNameArray {
return @[
@"FJDiscoverViewController",
@"FJProfileViewController",
];
}
@end
二.使用介绍
-
使用方法
A. 首先让navigationController
设置代理为[FJFNavigationControllerManager sharedInstance]
/**
设置 navigationController 的UINavigationControllerDelegate代理 为FJFNavigationControllerManager 单例
@param navigationController 遵循UINavigationControllerDelegate代理的 navigationController
*/
+ (void)setNavigationDelegateWithNavigationController:(UINavigationController *)navigationController;
/**
生成 navigationController 并 设置 UINavigationControllerDelegate代理 为FJFNavigationControllerManager 单例
@param viewControllerName 根界面名称
@return 生成 navigationController
*/
+ (UINavigationController *)navigationControllerWithViewControllerName:(NSString *)viewControllerName;
/**
生成 navigationController 并 设置 UINavigationControllerDelegate代理 为FJFNavigationControllerManager 单例
@param viewController 根界面
@return 生成 navigationController
*/
+ (UINavigationController *)navigationControllerWithRootViewController:(UIViewController *)viewController;
B. 在FJFViewControllerConfigureManager
中的vcNeedsNavBarHiddenNameArray
设置需要隐藏导航栏
的控制器名称
。
/************* ⚡️⚡️⚡️ 此处为需要隐藏导航栏的控制器 ⚡️⚡️⚡️ *************/
+ (NSArray<NSString *> *)vcNeedsNavBarHiddenNameArray {
return @[
@"FJDiscoverViewController",
@"FJProfileViewController",
];
}
- 集成方法:
静态:手动将FJFNavigationBarManager文件夹拖入到工程中。
github 链接
Demo地址: https://github.com/fangjinfeng/FJFNavigationBarManager
-
效果展示:
三. 原理分析
1. 原理简介
导航栏管理器(FJFNavigationControllerManager)
主要是通过设置navigationController
的UINavigationControllerDelegate
代理为[FJFNavigationControllerManager sharedInstance]单例
然后在代理方法
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
内去判断当前viewController
是否在需要隐藏的控制器数组(vcNeedsNavBarHiddenNameArray)
中,如果在就隐藏
,如果不在就显示
。同时通过
UINavigationController
的类别在load
函数里面进行导航栏隐藏
的方法交换
,
如果设置了隐藏导航栏
,就将导航栏隐藏标志位
置为YES
,否则置为NO
,通过这个标志位
来判断如果该navigationController
上的viewController
不是通过需要隐藏的控制器数组(vcNeedsNavBarHiddenNameArray)
而是通过自己的方法来隐藏导航栏
,就会崩溃输出提示log
。
2. 代码分析:
FJFNavigationControllerManager
的3
个类方法:
+ (UINavigationController *)navigationControllerWithViewControllerName:(NSString *)viewControllerName {
UIViewController *vc = [[NSClassFromString(viewControllerName) alloc] init];
NSAssert([vc isKindOfClass:[UIViewController class]], @"viewControllerName 必现是 UIViewController");
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:vc];
navController.delegate = (id)[FJFNavigationControllerManager sharedInstance];
return navController;
}
+ (UINavigationController *)navigationControllerWithRootViewController:(UIViewController *)viewController {
NSAssert([viewController isKindOfClass:[UIViewController class]], @"viewController 必现是 UIViewController");
UINavigationController *navVc = [[UINavigationController alloc] initWithRootViewController:viewController];
navVc.delegate = (id)[FJFNavigationControllerManager sharedInstance];
return navVc;
}
+ (void)setNavigationDelegateWithNavigationController:(UINavigationController *)navigationController {
NSAssert([navigationController isKindOfClass:[UINavigationController class]], @"navigationController 必现是 UINavigationController");
navigationController.delegate = (id)[FJFNavigationControllerManager sharedInstance];
}
主要用来生成navigationController
,并设置UINavigationControllerDelegate
代理为[FJFNavigationControllerManager sharedInstance]
。
UINavigationControllerDelegate
的代理方法- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
主要用来判断当前viewController
是否在需要隐藏的控制器数组(vcNeedsNavBarHiddenNameArray)
中,如果在就隐藏
,如果不在就显示
。
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
[self updateNavigationBarStatusWithNavigationController:navigationController willShowViewController:viewController animated:animated];
}
- (void)updateNavigationBarStatusWithNavigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
#ifdef DEBUG
[self checkAndThrowExceptionWithNavigationController:navigationController topViewController:viewController];
#endif
[navigationController setNavigationBarHidden:[self shouldNavigationController:navigationController hideNavigationBarOfViewController:viewController]
animated:animated];
viewController.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:(UIBarButtonItemStylePlain) target:nil action:nil];
#ifdef DEBUG
[navigationController fjf_resetHasCalledSetNavigationBarHiddenFlag]; //重置hidden flag
#endif
}
这里的shouldNavigationController
函数是用来判断当前viewController
是否在需要隐藏的控制器数组(vcNeedsNavBarHiddenNameArray)
中.
- (BOOL)shouldNavigationController:(UINavigationController *)navigationController hideNavigationBarOfViewController:(UIViewController *)viewController {
for (NSString *vcName in [FJFViewControllerConfigureManager vcNeedsNavBarHiddenNameArray]) {
if ([vcName isEqualToString:NSStringFromClass([viewController class])]) {
return YES;
}
}
return NO;
}
而checkAndThrowExceptionWithNavigationController:navigationController
函数则是通过navigationController
的fjf_hasCalledSetNavigationBarHidden
来获取导航栏是否隐藏标志位
来,判断使用者
是否通过其他方式
隐藏导航栏,如果是,则给出提示
。
- (void)checkAndThrowExceptionWithNavigationController:(UINavigationController *)navigationController topViewController:(UIViewController *)viewController {
#ifdef DEBUG
if ([navigationController fjf_hasCalledSetNavigationBarHidden]) {
NSString *tips = [NSString stringWithFormat:@"\n\n\n\n************************************************************\n 统一在此处实现导航栏的隐藏,禁止在该系列的navigationController的\n\n *** %@ ***\n\n 控制器中设置setNavigationBarHidden\n************************************************************\n\n\n\n", viewController];
NSLog(@"%@", tips);
NSAssert(NO, tips);
}
#endif
}
之后则调用navigationController
的fjf_resetHasCalledSetNavigationBarHiddenFlag
,重置标志位
。
这里判断使用者
是否通过其他方式
隐藏导航栏,主要是通过runtime
的方法交换
,交换了系统的设置导航栏显示与隐藏的函数setNavigationBarHidden:animated:
,来设置导航栏隐藏标志位
。
static void *hasSetNavigationBarHiddenKey = &hasSetNavigationBarHiddenKey;
@implementation UINavigationController (HasCalledTheMethod)
#pragma mark -------------------------- Life Circle
+ (void)load {
#ifdef DEBUG
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class selfClass = [self class];
SEL originalSelector = @selector(setNavigationBarHidden:animated:);
SEL swizzledSelector = @selector(fjf_hasCalledTheMethod_setNavigationBarHidden:animated:);
Method originalMethod = class_getInstanceMethod(selfClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(selfClass, swizzledSelector);
BOOL didAddMethod = class_addMethod(selfClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(selfClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
#endif
}
#pragma mark -------------------------- Public Methods
- (void)fjf_hasCalledTheMethod_setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
objc_setAssociatedObject(self, &hasSetNavigationBarHiddenKey, @(YES), OBJC_ASSOCIATION_ASSIGN);
[self fjf_hasCalledTheMethod_setNavigationBarHidden:hidden animated:animated];
}
- (void)fjf_resetHasCalledSetNavigationBarHiddenFlag {
objc_setAssociatedObject(self, &hasSetNavigationBarHiddenKey, @(NO), OBJC_ASSOCIATION_ASSIGN);
}
- (BOOL)fjf_hasCalledSetNavigationBarHidden {
return [objc_getAssociatedObject(self, hasSetNavigationBarHiddenKey) boolValue];
}
四. 总结
综上所述就是FJFNavigationBarManager
这个导航栏管理器
的一个设计思路
,核心代码量
也就一百来行
,只需配置下需要隐藏的界面名称,简单易用
如果你觉得你觉得这思路或是代码有什么问题,欢迎留言大家讨论下!如果觉得不错,麻烦给个喜欢或star,谢谢!