如何使用UINavigationController

1. 关于UINavigationController

1.1 对一个iOS开发者来说,使用系统的UINavigationController是一件很容易的事,例如下面的代码(这里举个例子)。

    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.backgroundColor = [UIColor whiteColor];
    self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[ViewController new]];
    [self.window makeKeyAndVisible];

1.2 效果

image.png

1.3 对于简单使用很容易,但是在实际开发中,对于导航栏的定制远远不止这些例如:

  • 实现侧滑效果
  • 实现全屏侧滑效果
  • 设置背景颜色
  • 设置背景图片
  • 实现导航栏透明效果
  • 实现导航栏渐变效果
  • 实现个别导航栏的样式

注意:对于侧滑效果来说,可以分为两种一种是融合,一种是平滑。

  • 导航栏自带屏幕边缘侧滑就是融合的效果。
  • 对于导航栏的平滑效果需要我们自己去实现。

2 平滑效果

2.1 平滑效果初体验

  • 将父类的 navigationBarHidden 隐藏可以达到此效果
    headerView.gif

问题:

  • 导航栏被隐藏了,系统的导航布局也就看不见了。
  • 屏幕边缘侧滑手势也失效了。

2.2 显示布局

这里提供一种思路,还有其他方法实现效果,后面会进行介绍

思路 :虽然系统的导航控制器NAV_A(这里将被隐藏的导航控制叫做NAV_A)被隐藏了,但是其栈结构还是存在的所有被加入的子控制器依然要遵循先进后出规则。

  • 由此我们如果要实现完整的导航栏的布局,必须是要有导航的支持,如果在新建一个导航NAV_B,将ViewController 子控制器添加到 NAV_B。
  • 此时 NAV_A 栈中还是空的,如果将被添加的NAV_B直接作为NAV_A 的子控制器;也就是导航控制器中的子控制全部是导航控制器,这个似乎不是很合理。
  • 此时如果新建一个UIViewController的 VC_TMP 控制器 将 NAV_B 作为 VC_TMP的子控制器,相当于用UIViewController的控制器包装 导航栏控制器。
  • 最后将 VC_TMP 添加到 NAV_A 控制器中。

2.2.1 图示

  • 概览
image.png
  • 封装过程


    image.png

2.2.2 代码演示

  • 首先建立一个自控制器 MLWrapViewController : UIViewController

.h

//
//  MLWrapViewController.h
//  MLNavigationController
//
//  Created by  MrDML on 2019/6/27.
//  Copyright © 2019 top.objccn. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface MLWrapViewController : UIViewController
+ (instancetype)wrapWithViewController:(UIViewController *)viewController;
- (instancetype)initWithWrapViewController:(UIViewController *)viewController;
@end

NS_ASSUME_NONNULL_END

.m

//
//  MLWrapViewController.m
//  MLNavigationController
//
//  Created by MrDML on 2019/6/27.
//  Copyright © 2019 top.objccn. All rights reserved.
//

#import "MLWrapViewController.h"
#import "MLWrapNavigationController.h"

@interface MLWrapViewController ()
@property (nonatomic, strong)  MLWrapNavigationController  * wrapNavigationController;
@end

@implementation MLWrapViewController


+ (instancetype)wrapWithViewController:(UIViewController *)viewController{
    return [[self alloc] initWithWrapViewController:viewController];
}


- (instancetype)initWithWrapViewController:(UIViewController *)viewController{
    self = [super init];
    if (self) {
    
        // 1. 初始化封装后的NavigationController
        self.wrapNavigationController =  [MLWrapNavigationController wrapNavigationController:viewController];
        
        // 2. 封装后的导航栏,添加为MLWrapViewController 控制器的子控制器
        [self addChildViewController:self.wrapNavigationController];
        [self.wrapNavigationController didMoveToParentViewController:self];
        
        
    }
    return self;
    
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
   //  1. 添加视图
    [self.view addSubview:self.wrapNavigationController.view];
    
    // 2. 设置自动布局
    self.wrapNavigationController.view.autoresizingMask= UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
    // 3. 设置frame
    self.wrapNavigationController.view.frame = self.view.frame;
  
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end
  • 建立被包装的导航栏 MLWrapNavigationController : UINavigationController

.h

//
//  MLWrapNavigationController.h
//  MLNavigationController
//
//  Created by Alan.Dai on 2019/6/27.
//  Copyright © 2019 top.objccn. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface MLWrapNavigationController : UINavigationController
- (instancetype)initWithWrapNavigationController:(UIViewController *)viewcontroller;
+ (instancetype)wrapNavigationController:(UIViewController *)viewcontroller;
@end

NS_ASSUME_NONNULL_END

.m

//
//  MLWrapNavigationController.m
//  MLNavigationController
//
//  Created by Alan.Dai on 2019/6/27.
//  Copyright © 2019 top.objccn. All rights reserved.
//

#import "MLWrapNavigationController.h"
#import "MLNavigationController.h"
#import "MLWrapViewController.h"

@interface MLWrapNavigationController ()

@end

@implementation MLWrapNavigationController


- (instancetype)initWithWrapNavigationController:(UIViewController *)viewcontroller{
    
    self = [super init];
    if (self) {
        self.viewControllers =@[viewcontroller];
        
        if ([viewcontroller isKindOfClass:[UITabBarController class]]) {
            [self setNavigationBarHidden:YES animated:NO];
        }
    }
    return self;
}

+ (instancetype)wrapNavigationController:(UIViewController *)viewcontroller{
    return [[self alloc] initWithWrapNavigationController:viewcontroller];
}


- (void)viewDidLoad {
    [super viewDidLoad];
    
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

  • 设置根控制器
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.backgroundColor = [UIColor whiteColor];
    self.window.rootViewController = [[MLNavigationController alloc] initWithRootViewController:[ViewController alloc]];
    [self.window makeKeyAndVisible];
    
    return YES;
}
  • push 操作
- (void)click:(UIButton *)sender{
    TestVC_One *oneVC = [[TestVC_One alloc] init];
    NSLog(@"%@",self.navigationController); // MLWrapNavigationController
    [self.navigationController pushViewController:oneVC animated:YES];
    
}

2.2.3 效果展示与问题

navgation.gif

2.3 问题

  1. 通过上述效果可以发现以下几个问题
  • 导航栏虽然出现了,但是并没有达到融合的效果。
  • 屏幕边缘侧滑也可以了,但是这个和最初的效果完全一样,就是默认系统的效果
  1. 为什么会出现上述的问题? 我不是已经包装了吗?怎么 push之后和系统的完全一样?

2.3.1 navigationController 是谁?

  • 2.3 的问题先抛开不去理会,先明确一个问题 navigationController 是谁?
  • 首先我们需要看 在哪里进行的push 操作 ViewController 是导航的根控制器
self.window.rootViewController = [[MLNavigationController alloc] initWithRootViewController:[ViewController alloc]];
- (void)click:(UIButton *)sender{
    
    TestVC_One *oneVC = [[TestVC_One alloc] init];
    NSLog(@"%@",self.navigationController); // MLWrapNavigationController
    // 这里获取的肯定是  MLWrapNavigationController 因为 跟控制器是添加到这个导航中去的
    [self.navigationController pushViewController:oneVC animated:YES];
    
}
  • 通过打印self.navigationController 我们可以知道在ViewController 中的导航栏是MLWrapNavigationController,如果产生疑问,请看下图。

  • 图示


    image.png

    image.png
  • 分析

  • viewControllerMLWrapNavigationController 管理,获取的导航栏self.navigationController自然是 MLWrapNavigationController
  • MLWrapNavigationController 被添作为MLWrapViewController的子控制器 然而MLWrapViewController是被导航MLNavigationController管理的,那么如果在MLWrapNavigationController中获取导航栏self.navigationController自然是MLNavigationController
  • 明确上面self.navigationController是谁后,和2.3问题 的问题有什么关系?

  • 因为我们在viewController 中进行[self.navigationController pushViewController:oneVC animated:YES];操作后会调用MLWrapNavigationController 中的- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated方法

这个方法是父类的,在之前的代码中我们并没有实现,覆盖系统的方法,系统方法如下

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
   [super pushViewController:viewController animated:animated];
}
  • 通过上述的代码我们可以看到super 值得其实就是父类 UINavigationController
  • 也就是说在push操作的时候,还是走的系统的提供的默认方法 还是用UINavigationController进行的push
  • 其实我们在MLWrapNavigationController 类中获取的导航self.navigationController 其实是MLNavigationController
  • MLWrapNavigationController 是被 MLNavigationController 导航进行管理的。
  • 如果将 MLWrapNavigationController 作为一个普通的控制器看的话,那么如果在一个普通的控制器中进行push操作,
  • 首先你肯定要获取导航
  • 使用导航进行push
  • 通过上述分析,我们需要实现该方法,并且做出如下修改
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{

    // 获取导航栏
    MLNavigationController *nav = (MLNavigationController*)self.navigationController;

    // 进行push 操作
    [nav pushViewController:viewController animated:animated];
}
  • 效果展示
navgation-1693626.gif

2.3.2 问题

  • 上述效果展示之后,发现push操作之后,导航栏标题没出现
  • 原因其实是push 的是一个普通的控制器UIViewController,这个肯定是行不通的。
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
     MLNavigationController *nav = (MLNavigationController *)self.navigationController;
     [nav pushViewController:viewController animated:animated];
}
  • 我们应该push 一个MLWrapViewController类型的控制器 原因是因为该控制内部有一个导航栏的自控制器。
  • 所以我们应该push一个带有导航栏的普通控制器。

2.3.3封装push 的子控制器

  • 代码演示
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{

    // 1. 获取导航栏
    MLNavigationController *nav = (MLNavigationController *)self.navigationController;

    // 2. 封装普通的控制器 UIViewController -> MLWrapViewController
     MLWrapViewController *wrapViewController = [MLWrapViewController wrapWithViewController:viewController];
    // 3. push 操作
     [nav pushViewController:wrapViewController animated:animated];


}
  • 效果展示


    navgation-1699573.gif
  • 通过改进push 操作之后导航栏标题显示正常了。

2.3.4 问题

  • 系统的导航栏在push 之后无法返回。
  • 边缘侧滑依然没有效果。
  • 系统的导航栏在push操作之后导航的左上角有默认的返回按钮,自己实现的没有

2.3.5 分析系统的导航栏在push 之后无法返回

1. 导航栏无法返回究其原因我们在控制器TestVC_One

- (void)back:(UIButton *)sender{
    [self.navigationController popViewControllerAnimated:YES];
}
  • 我们知道此时的 self.navigationController 其实是MLWrapNavigationController

  • MLWrapNavigationController : UINavigationController ,MLWrapNavigationController继承UINavigationController

  • 当调用- (UIViewController *)popViewControllerAnimated:(BOOL)animated 会执行系统默认的方法

- (UIViewController *)popViewControllerAnimated:(BOOL)animated{
    [super popViewControllerAnimated:animated];
}
  • 我们所看到的是点击返回之后没有效果,其实popViewControllerAnimated这个方法以经执行了。
  • 原因是因为在MLWrapNavigationController只有一个控制器被压栈那就是ViewController ,根据之前封装的思路这个导航控制器中之后一个子控制器被压栈,也就是self.viewControllers =@[viewcontroller];

2 . 解决上述问题

  • MLWrapNavigationController中的获取的导航控制器self.navigationController其实是MLNavigationController 而不是 super

  • 修改代码

- (UIViewController *)popViewControllerAnimated:(BOOL)animated{
    MLNavigationController *nav = (MLNavigationController *)self.navigationController;
    return [nav popViewControllerAnimated:animated];
}

3 效果展示

navgation-1701472.gif

4 我们知道导航栏常用的pop方式有下列三种

// 返回上一个控制器
- (UIViewController *)popViewControllerAnimated:(BOOL)animated{
    MLNavigationController *nav = (MLNavigationController *)self.navigationController;
    return [nav popViewControllerAnimated:animated];
}

// 返回到指定的控制器
- (NSArray<UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated{
    
    // 1.这里跳转到指定的控制器,并不是跳转到普通的控制器 UIViewController 而是需要跳转到 MLWrapViewController 类型的控制器
    // 2. 所以这里需要获取 viewController 这个普通的控制器 对应的 MLWrapViewController 类型的控制器
    return [self.navigationController popToViewController:? animated:animated];
}

// 返回到根控制器
- (NSArray<UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated{
    
    return [self.navigationController popToRootViewControllerAnimated:animated];
}
  • 需要注意的是- (NSArray<UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated这个方法

  • 其中的viewController 并不能直接使用,而获取对应MLWrapViewController的控制器

  • 解决方法,在本控制器中获取到 MLWrapViewController 进行保存,这里通过Runtime 动态添加属性

  • UIViewContrller添加分类UIViewController+MLNavigationController.h
    .h

//
//  UIViewController+MLNavigationController.h
//  MLNavigationController
//
//  Created by MrDML on 2019/6/28.
//  Copyright © 2019 top.objccn. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@class MLWrapViewController;

@interface UIViewController (MLNavigationController)
/**
 封装之后的控制器
 */
@property (nonatomic, strong)  MLWrapViewController *wrapViewController;


@end

NS_ASSUME_NONNULL_END

.m

//
//  UIViewController+MLNavigationController.m
//  MLNavigationController
//
//  Created by MrDML on 2019/6/28.
//  Copyright © 2019 top.objccn. All rights reserved.
//

#import "UIViewController+MLNavigationController.h"
#import <objc/Runtime.h>

@implementation UIViewController (MLNavigationController)

- (void)setWrapViewController:(MLWrapViewController *)wrapViewController{
    
    objc_setAssociatedObject(self, @selector(wrapViewController), wrapViewController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


- (MLWrapViewController *)wrapViewController{
    
    return objc_getAssociatedObject(self, @selector(wrapViewController));
}

@end

5 修改之前的代码

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{

    // 1. 获取导航栏
    MLNavigationController *nav = (MLNavigationController *)self.navigationController;

    // 2. 封装普通的控制器 UIViewController -> MLWrapViewController
     MLWrapViewController *wrapViewController = [MLWrapViewController wrapWithViewController:viewController];

    // 3. 将封装后的控制器进行保存
    viewController.wrapViewController = wrapViewController;
    
    [nav pushViewController:wrapViewController animated:animated];


}NS_ASSUME_NONNULL_END
// 返回到指定的控制器
- (NSArray<UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated{
    
    // 1.这里跳转到指定的控制器,并不是跳转到普通的控制器 UIViewController 而是需要跳转到 MLWrapViewController 类型的控制器
    // 2. 所以这里需要获取 viewController 这个普通的控制器 对应的 MLWrapViewController 类型的控制器  
    MLWrapViewController * wrapViewController = viewController.wrapViewController;
    return [self.navigationController popToViewController:wrapViewController animated:animated];
}

2.3.6 显示导航栏左上角的返回

  1. 实现代码
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{    
    // 1. 获取导航栏
    MLNavigationController *nav = (MLNavigationController *)self.navigationController;
    // 2. 封装普通的控制器 UIViewController -> MLWrapViewController
     MLWrapViewController *wrapViewController = [MLWrapViewController wrapWithViewController:viewController];
    // 3. 将封装后的控制器进行保存
    viewController.wrapViewController = wrapViewController;

    // 4. 给控制器添加一个导航添加返回按钮
    viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(leftBack:)];
    
    // 5. 执行push操作
    [nav pushViewController:wrapViewController animated:animated];

}

- (void)leftBack:(id)button{
    [self popViewControllerAnimated:YES];
}
  
  1. 通过上述代码,我们相当于把返回按钮给写死了,如果想在子控制器实现自定义的返回按钮,那么就会比较麻烦,如果我们能够提供一个可自定义的api,那么就会很灵活了,子控制器是UIViewController然后在这个控制器中并没有这样的接口,所以我们使用类扩展添加分类的方法解决此问题
  2. UIViewController 添加分类
    .h
//
//  UIViewController+MLCategory.h
//  MLNavigationController
//
//  Created by MrDML on 2019/6/28.
//  Copyright © 2019 top.objccn. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIViewController (MLCategory)


/**
 实现自定义导航栏左侧按钮

 @param target target description
 @param action action description
 @return return value description
 */
- (UIBarButtonItem *)ml_getCustomeLeftBarButtonItemWithTarget:(id)target action:(SEL)action;
@end

NS_ASSUME_NONNULL_END

.m

//
//  UIViewController+MLCategory.m
//  MLNavigationController
//
//  Created by MrDML on 2019/6/28.
//  Copyright © 2019 top.objccn. All rights reserved.
//

#import "UIViewController+MLCategory.h"

@implementation UIViewController (MLCategory)


- (UIBarButtonItem *)ml_getCustomeLeftBarButtonItemWithTarget:(id)target action:(SEL)action{
    
    // 提供一个默认的实现
    UIButton *sender = [UIButton buttonWithType:UIButtonTypeCustom];
    [sender setTitle:@"返回" forState:UIControlStateNormal];
    [sender setImage:[UIImage imageNamed:@"left_back"] forState:UIControlStateNormal];
    [sender addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
    [sender setTitleColor:[UIColor colorWithRed:18/255.0 green:150/255.0 blue:219/255.0 alpha:1] forState:UIControlStateNormal];
    [sender sizeToFit];
    

    return [[UIBarButtonItem alloc] initWithCustomView:sender];
    
}
@end
  1. 修改之前的代码
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
    
    // 1. 获取导航栏
    MLNavigationController *nav = (MLNavigationController *)self.navigationController;

    // 2. 封装普通的控制器 UIViewController -> MLWrapViewController
     MLWrapViewController *wrapViewController = [MLWrapViewController wrapWithViewController:viewController];

    // 3. 将封装后的控制器进行保存
    viewController.wrapViewController = wrapViewController;
    
    
    // 4. 给控制器添加一个导航添加返回按钮
    
    if ([viewController respondsToSelector:@selector(ml_getCustomeLeftBarButtonItemWithTarget:action:)]) { // 自定义
        viewController.navigationItem.leftBarButtonItem = [viewController ml_getCustomeLeftBarButtonItemWithTarget:self action:@selector(leftBack:)];
    }else{ // 默认
         viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(leftBack:)];
    }
    [nav pushViewController:wrapViewController animated:animated];
}
  1. 在子控制器 TestVC_One 实现自定义的方法
- (UIBarButtonItem *)ml_getCustomeLeftBarButtonItemWithTarget:(id)target action:(SEL)action{
    
    // 这里采用默认实现的
    return [super ml_getCustomeLeftBarButtonItemWithTarget:target action:action];
}

2.3.7 如何在子控制器中获取根导航控制器MLNavigationController

  1. 在UIViewController+MLNavigationController.h 新增扩展方法
    .h
/**
 控制器的根导航控制器
 */
@property (nonatomic, weak, readonly) MLNavigationController *ml_navigationController;

.m

- (MLNavigationController *)ml_navigationController{
    
    UIViewController *vc = self;
    
    /**
     vc <--> UIViewContrller ; vc.navigationController  -->  MLWrapNavigationController
     vc <--> MLWrapNavigationController ; vc.navigationController === (导航栏控制器的导航)  -->  MLNavigationController
     */
    while (vc && ![vc isKindOfClass:[MLNavigationController class]]) {
        vc = vc.navigationController;
    }
    
   MLNavigationController  *nav = (MLNavigationController *)vc;
    
    return nav;
}
  1. 在子控制器中获取
 NSLog(@"->%@",self.navigationController); // <MLWrapNavigationController: 0x7f943e86a200>
    NSLog(@"->%@",self.navigationController.navigationController); // <MLNavigationController: 0x7f943e84f400>
    // 获取根导航控制器
    NSLog(@"->%@",self.ml_navigationController); // <MLNavigationController: 0x7f943e84f400>
image.png
  1. 阶段完成效果
navgation-1707893.gif

2.4 实现侧滑
2.4.1 实现系统屏幕边缘侧滑

  1. 代码实现
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [super setDelegate:self];
    [super setNavigationBarHidden:YES animated:NO];
    self.navigationBarHidden = YES;
    
    
    // 手势识别器负责将顶视图控制器弹出导航堆栈
    NSLog(@"====>%@", self.interactivePopGestureRecognizer.delegate);
    /**
     <UIScreenEdgePanGestureRecognizer: 0x7feb20d143a0; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7feb20d136c0>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7feb20d004d0>)>>
     
     */
    
    
    // 设置手势代理
    UIGestureRecognizer *gesture = self.interactivePopGestureRecognizer;
    
    // 想知道UIGestureRecognizer 的target 对象是谁
    NSLog(@"gesture delegate = %@",gesture.delegate); // _UINavigationInteractiveTransition
    
     gesture.delaysTouchesBegan = YES;
    
     gesture.delegate = self;
    
     gesture.enabled = YES;
    

}
  1. 效果
    navigation.gif

    2.4.2 全屏自定义侧滑
  2. UIScreenEdgePanGestureRecognizer是用来处理屏幕侧滑手势的一个类
  3. 我们在导航栏控制器中通过获取self.interactivePopGestureRecognizer 打印,得到如下信息。
 <UIScreenEdgePanGestureRecognizer: 0x7feb20d143a0; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7feb20d136c0>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7feb20d004d0>)>>
  1. 通过打印信息我可以确定一些信息
  • 通过UIScreenEdgePanGestureRecognizer可以确定是屏幕边缘侧滑手势.

  • 处理手势的方法actionhandleNavigationTransition

  • target_UINavigationInteractiveTransition 它是接收器识别手势时发送的动作消息的接收者,也就是说 handleNavigationTransition 属于 _UINavigationInteractiveTransition这个对象的方法。

  • 我们又可以知道interactivePopGestureRecognizerUIGestureRecognizer 这种类型的对象

  • 继承关系 UIScreenEdgePanGestureRecognizer : UIPanGestureRecognizer ;UIPanGestureRecognizer : UIGestureRecognizer

  • 所以我们可以获取该UIGestureRecognizer对象,并且获取他的delegate 发现 delegat就是_UINavigationInteractiveTransition

  • 通过打印gesture.viewself.view 发现两者是同一个,就是本导航控制器的view视图

 NSLog(@"gesture.view  = %@",gesture.view);
    //  gesture.view  = <UILayoutContainerView: 0x7fb6aaf0bf60; frame = (0 0; 414 896); autoresize = W+H; gestureRecognizers = <NSArray: 0x600003cb15c0>; layer = <CALayer: 0x6000032a0be0>>
    NSLog(@"self.view = %@",self.view);
    // self.view = <UILayoutContainerView: 0x7fb6aaf0bf60; frame = (0 0; 414 896); autoresize = W+H; gestureRecognizers = <NSArray: 0x600003cb15c0>; layer = <CALayer: 0x6000032a0be0>>
  1. 通过上诉的分析我们可以写出这样的代码

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [super setDelegate:self];
    [super setNavigationBarHidden:YES animated:NO];
    self.navigationBarHidden = YES;
    
    
    // 手势识别器负责将顶视图控制器弹出导航堆栈
    NSLog(@"====>%@", self.interactivePopGestureRecognizer.delegate);
   
    /**
     <UIScreenEdgePanGestureRecognizer: 0x7feb20d143a0; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7feb20d136c0>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7feb20d004d0>)>>
     
     */
    
    // 设置手势代理
    UIGestureRecognizer *gesture = self.interactivePopGestureRecognizer;
    
    // 想知道UIGestureRecognizer 的target 对象是谁
    NSLog(@"gesture delegate = %@",gesture.delegate); // _UINavigationInteractiveTransition
    
    NSLog(@"gesture.view  = %@",gesture.view);
    //  gesture.view  = <UILayoutContainerView: 0x7fb6aaf0bf60; frame = (0 0; 414 896); autoresize = W+H; gestureRecognizers = <NSArray: 0x600003cb15c0>; layer = <CALayer: 0x6000032a0be0>>
    NSLog(@"self.view = %@",self.view);
    // self.view = <UILayoutContainerView: 0x7fb6aaf0bf60; frame = (0 0; 414 896); autoresize = W+H; gestureRecognizers = <NSArray: 0x600003cb15c0>; layer = <CALayer: 0x6000032a0be0>>
    
    
    // 自定义手势
    // 将手势添加到view上
    UIPanGestureRecognizer *panGester = [[UIPanGestureRecognizer alloc] initWithTarget:gesture.delegate action:NSSelectorFromString(@"handleNavigationTransition:")];
    
   
    // 其实就是本控制器的view视图
    [gesture.view addGestureRecognizer:panGester];
    
    gesture.delaysTouchesBegan = YES;
    
    panGester.delegate = self;

    // 一旦触发手势本控制器view的手势,就会由gesture.delegate 对象去执行handleNavigationTransition: 这个方法 完成手势的处理
    NSLog(@"1.%@",self.viewControllers);
}

#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    
    // 如果只有一个控制器,就不需要侧滑了
    if(self.childViewControllers.count == 1) {
        return NO;
    }
    return YES;
    
}
  1. 一旦触发手势本控制器view的手势,就会由gesture.delegate对象去执行handleNavigationTransition: 这个方法 完成手势的处理
  2. 效果


    navgation-1711442.gif

总结

实现的大致思路是这样的,还有很多细节需要处理。这里就不详细书写了,这种实现思路曾在一个demo看到是这样实现的,这里整理一下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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