iOS 导航栏管理器(FJFNavigationBarManager)

一. 前言

我们产品需求中很经常会碰到某个界面是需要隐藏导航栏或者自定义导航栏,但是跳转到下个界面又需要显示导航栏,更有甚者,比如说当前界面是需要隐藏导航栏的,这个界面可以跳转到其他十来个界面,其中有一半的界面是需要隐藏导航栏一半是需要显示导航栏的,这样导航栏处理起来就很麻烦,而且导航栏处理代码散落在各个控制器界面,后期寻找和修改起来也麻烦。

举个🌰 :

  • 当前发现界面隐藏导航栏
  • 同时发现界面可以跳转到店铺界面我的界面
  • 店铺界面是显示导航栏,而我的界面隐藏导航栏

代码展示:

#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];
}

效果展示:

FJFNavigationBarManager-NoManager.gif

我们可以看到:尽管我们在发现界面viewWillAppearviewWillDisappear做了如下处理

- (void)viewWillAppear:(BOOL)animated {
    [self.navigationController setNavigationBarHidden:YES animated:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    [self.navigationController setNavigationBarHidden:NO animated:animated];
}

但是跳转到我的界面这时导航栏显示还是有问题,尤其是手势滑动返回的时候,还是显得不协调。
这种情况处理起来就相对比较麻烦,因为我的界面viewWillAppearviewWillDisappear也做了处理,这就需要发现界面我的界面的两者配合起来处理才能达到协调的目的。

但是通过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

  • 效果展示:
    FJFNavigationBarManager-Manager.gif

三. 原理分析

1. 原理简介

  • 导航栏管理器(FJFNavigationControllerManager)主要是通过设置navigationControllerUINavigationControllerDelegate代理为[FJFNavigationControllerManager sharedInstance]单例

  • 然后在代理方法- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated内去判断当前viewController是否在需要隐藏的控制器数组(vcNeedsNavBarHiddenNameArray)中,如果在就隐藏,如果不在就显示

  • 同时通过UINavigationController的类别在load函数里面进行导航栏隐藏方法交换
    如果设置了隐藏导航栏,就将导航栏隐藏标志位置为YES,否则置为NO,通过这个标志位来判断如果该navigationController上的viewController不是通过需要隐藏的控制器数组(vcNeedsNavBarHiddenNameArray)而是通过自己的方法来隐藏导航栏,就会崩溃输出提示log

2. 代码分析:

  • FJFNavigationControllerManager3个类方法:
+ (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函数则是通过navigationControllerfjf_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
}

之后则调用navigationControllerfjf_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这个导航栏管理器的一个设计思路核心代码量也就一百来行,只需配置下需要隐藏的界面名称,简单易用

image.png

如果你觉得你觉得这思路或是代码有什么问题,欢迎留言大家讨论下!如果觉得不错,麻烦给个喜欢或star,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容