美文网首页iOS开发进阶程序员iOS Developer
iOS 导航栏管理器(FJFNavigationBarManag

iOS 导航栏管理器(FJFNavigationBarManag

作者: 果哥爸 | 来源:发表于2018-07-10 17:59 被阅读12次

    一. 前言

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

    举个🌰 :

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

    代码展示:

    #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,谢谢!

    相关文章

      网友评论

        本文标题:iOS 导航栏管理器(FJFNavigationBarManag

        本文链接:https://www.haomeiwen.com/subject/xrwpnxtx.html