JTNavigationController

1,227 阅读3分钟

随着现在应用的UI越来越多样化,导航栏的颜色不再是单一颜色,还有些需要隐藏navigationBar的需求,所以UINavigationControllernavigationBar在不同颜色或隐藏的情况下切换效果显得不够平滑,越来越多的应用也开始使用类似网易新闻,网易云音乐,淘宝这种类似的切换效果.

一、介绍

005SFaicgw1f34gt62yzcg308w0fs1kx.gif

005SFaicgw1f34gt87ndag308w0fshdu.gif

效果的实现大致分3种方式

  • 使用自定义navigationBar.淘宝,网易新闻,达令等使用的是这种 灵活度高,实现的效果好.但是缺点也明显,自定义的navigationBar使用起来没有原生的使用起来方便.如果是已有项目要转换的话会有点麻烦.
  • 用截图的办法,在push到下一个页面时,截取屏幕,在使用edgePan来pop时看到的就是背后的截图,也能实现这种效果.京东,天猫等使用的是这种 实现起来相对比较简单,缺点是截图是静态的,在侧滑返回时看到的不是即时的内容.而且这种方法只是在pop时有效果,push时并没有,还是原生push的渐变效果.
  • 使用了一种比较特别,比较巧妙的办法实现的,也就是网易云音乐的实现方法,后面会分析一下这种实现 集合了前两种的优点,有易集成,效果也好的特点.并且不影响原有的导航栏的navigationItem和其他navigationBar的属性,包括设置的barTintColor,backgroundImage等.

目前存在问题:隐藏TabBar其实就是用控制器本身的NavigationController来Push就可以实现不隐藏,而不用 根控制器NavigationController来Push。

005SFaicgw1f34gt54hwpj30hp0bydgz.jpg

现在就能打开看出来个大概了,看到这的时候我就知道网易云音乐原来是使用了一个比较巧的方法,简单来说这个方法具体的流程就是:

  • 首先根控制器是一个导航控制器,这个导航控制器管理所有的页面的push和pop操作,并且这个导航控制器的导航栏navigationBar是隐藏的,并且是用setNavigationBarHidden:的方式隐藏,而不是navgationBar.hidden = YES,因为setNavigationBarHidden:的方式其实是直接将navigationBar给移除了,而navgationBar.hidden = YES只是让navigationBar变透明了.
  • 这个根的UINavigationControler下面还有一个UIViewController,是因为如果我们需要显示的界面本身也是一个UINavigationControler,那就会出问题了,UINavigationControler嵌套UINavigationControler的方式是不被允许的,和后面第三条相似.所以要进行包装.
  • 然后每当push一个ViewControler时,给这个ViewController进行了包装,先在外面包了一个UINavigationController,然后再在UINavigationControler外包一个UIViewController,这样做的原因是UINavigationControler不能push另一个UINavigationControler,所以需要再包一层UIViewController.
  • 最后我们真真正正在界面上看到的导航栏就是属于这个包在UIViewControler外的UINavigationControler的.因为每个页面都有属于自己的导航控制器,然后再有最外层的UINavigationControler来管理页面的push和pop操作,也就实现了我们想要的效果.
                              UIWindow
                                 |
                        UINavigationController
                                 |
                          UIViewController
                                 |
                         UITabBarController
                                 |
                        UINavigationController
                                 |
                          UIViewController
                                 |
                        UINavigationController
                                 |
                          UIViewController

最外层的UINavigationControler是固定不变的,用来操作push和pop操作,UITabBarControllerviewControllers也存放的是UINavigationControler,那么这个UINavigationControlerrootViewController也就需要进行一次UIViewController-UINavgationController的包装.也就形成了目前的这个结构.

二、分析

1、全局pop

UIPanGestureRecognizer替换原有interactivePopGestureRecognizer手势,在UINavigationControllerDelegate代理方法didShowViewControllerself.view上添加手势。

#import "JTNavigationController.h"

@interface JTNavigationController ()<UINavigationControllerDelegate>
@property (nonatomic, strong) UIPanGestureRecognizer *popPanGesture;
@property (nonatomic, strong) id popGestureDelegate;
@end

@implementation JTNavigationController
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController {
    if (self = [super initWithRootViewController:rootViewController]) {
        self.viewControllers = @[rootViewController];
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.delegate = self;
    self.popGestureDelegate = self.interactivePopGestureRecognizer.delegate;
    SEL action = NSSelectorFromString(@"handleNavigationTransition:");
    self.popPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self.popGestureDelegate action:action];
    self.popPanGesture.maximumNumberOfTouches = 1;
}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    BOOL isRootVC = viewController == navigationController.viewControllers.firstObject;
    if (isRootVC) {
        [self.view removeGestureRecognizer:self.popPanGesture];
    } else {
        [self.view addGestureRecognizer:self.popPanGesture];
    }
}
@end

2、push和pop操作JTNavigationController

- (instancetype)initWithRootViewController:(UIViewController *)rootViewController {
    if (self = [super init]) {
    // 指定rootViewController的导航栏为JTNavigationController
        rootViewController.jt_navigationController = self;
        self.viewControllers = @[[JTWrapViewController wrapViewControllerWithViewController:rootViewController]];
    }
    return self;
}

通过VC分类绑定属性

#import "UIViewController+JTNavigationExtension.h"

#import <objc/runtime.h>

@implementation UIViewController (JTNavigationExtension)

- (BOOL)jt_fullScreenPopGestureEnabled {
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setJt_fullScreenPopGestureEnabled:(BOOL)fullScreenPopGestureEnabled {
    objc_setAssociatedObject(self, @selector(jt_fullScreenPopGestureEnabled), @(fullScreenPopGestureEnabled), OBJC_ASSOCIATION_RETAIN);
}

- (JTNavigationController *)jt_navigationController {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setJt_navigationController:(JTNavigationController *)navigationController {
    objc_setAssociatedObject(self, @selector(jt_navigationController), navigationController, OBJC_ASSOCIATION_ASSIGN);
}
@end