前言:最近两天,从git上下载了一个项目,想要回滚版本的时候,居然想不起来回滚命令了,可当时用的那么熟,终究还是忘了!!痛定思痛,好记性不如烂笔头,还是需要把所学的内容记下来比较好,这样忘了也能有个地方来查找,看别人的blog学习新知识,看自己的笔记去复习旧知识.
在很多商业级的应用中都会增加全屏滑动手势,所以我还是自己学习了一种方法,是一种轻量级:给UINavigationController写一个分类,在需要的地方直接引入头文件即可,并不需要额外代码.
//
// UINavigationController+MSLEx.h
// 全屏右滑手势
//
// Created by lirenqiang on 16/7/8.
// Copyright © 2016年 lirenqiang. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UINavigationController (MSLEx)
@property (nonatomic, strong, readonly) UIPanGestureRecognizer * msl_popGestureRecognizer; //要添加到push出来的控制器上的手势.
@end
上面是分类的头文件,既然是一个分类,那么我们可以添加属性,但不是不会帮我们生成setter和getter方法.所以我们会在.m中用到运行时的关联对象.
#import "UINavigationController+MSLEx.h"
#import <objc/runtime.h>
@interface MSLFullScreenPopGestureRecognizerDelegate : NSObject <UIGestureRecognizerDelegate>
//用来保存push出需要添加手势控制器的navigationController.
@property (nonatomic, weak) UINavigationController * navigationController;
@end
@implementation MSLFullScreenPopGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
//判断当前的navigationController中的子控制器是否大于1,如果只有一个根控制器,我们就不需要执行该手势了.即返回NO.
if (self.navigationController.viewControllers.count <= 1) {
return NO;
}
//判断当前的navigationController是否正在进行转场动画,如果是,那么同样取消掉手势.
if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
return NO;
}
//判断手势在View上的移动的point的x,如果是小于等于0,那么表示手指正在向左滑,取消手指的响应.
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
if (translation.x <= 0) {
return NO;
}
//经过一系列的排除之后,返回YES
return YES;
}
@end
上面是.m文件,我们增加了一个类,遵守了UIGestureRecognizerDelegate代理方法,我们声明了一个属性保存navigationController.在实现文件中,实现了一下询问手势是否触发时执行的方法.返回YES,即手势触发,NO,就是手势没有效果.
@implementation UINavigationController (MSLEx)
+ (void)load {
Method originalMethod = class_getInstanceMethod([self class], @selector(pushViewController:animated:));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(msl_pushViewController:animated:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
- (void)msl_pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.msl_popGestureRecognizer]) {
[self.interactivePopGestureRecognizer.view addGestureRecognizer:self.msl_popGestureRecognizer];
NSArray * targets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
id internalTarget = [targets.firstObject valueForKey:@"target"];
SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
self.msl_popGestureRecognizer.delegate = [self msl_fullScreenPopGestureRecognizerDelegate];
[self.msl_popGestureRecognizer addTarget:internalTarget action:internalAction];
}
if (![self.viewControllers containsObject:viewController]) {
[self msl_pushViewController:viewController animated:YES];
}
}
- (MSLFullScreenPopGestureRecognizerDelegate *)msl_fullScreenPopGestureRecognizerDelegate {
MSLFullScreenPopGestureRecognizerDelegate * delegate = objc_getAssociatedObject(self, _cmd);
if (!delegate) {
delegate = [[MSLFullScreenPopGestureRecognizerDelegate alloc] init];
delegate.navigationController = self;
objc_setAssociatedObject(self, _cmd, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return delegate;
}
- (UIPanGestureRecognizer *)msl_popGestureRecognizer {
UIPanGestureRecognizer * popGestureRecognizer = objc_getAssociatedObject(self, _cmd);
if (!popGestureRecognizer) {
popGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
popGestureRecognizer.maximumNumberOfTouches = 1;
objc_setAssociatedObject(self, _cmd, popGestureRecognizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return popGestureRecognizer;
}
@end
上面这个实现是接在MSLFullScreenPopGestureRecognizerDelegate该类实现的下面的,这样正好是完整的一个分类的.m文件所有的内容. 我们首先在load中,使用了一个交换方法实现的底层方法: method_exchangeImplementations(originalMethod, swizzledMethod); 这样我们就可以在当系统push一个控制器的时候,执行我们的方法,从而添加一些我们想要的额外的操作(给控制器添加手势),执行完毕后,我们接着再调用自己的方法,那么就会调用系统自身的实现(因为之前我们做了方法的实现交换), 下面来看我们自己重写的 *- (void)msl_pushViewController:(UIViewController **)viewController animated:(BOOL)animated;
- (void)msl_pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
//首先对当前的View进行判断是否有我们自己要添加的手势,如果没有,进入if体内.
if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.msl_popGestureRecognizer]) {
//在这里,我们添加自己的手势.
//interactivePopGestureRecognizer: 该属性表示的是:负责popnavigation栈中最顶层的控制器的手势识别器.
[self.interactivePopGestureRecognizer.view addGestureRecognizer:self.msl_popGestureRecognizer];
NSArray * targets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
id internalTarget = [targets.firstObject valueForKey:@"target"];
SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
//给手势添加代理.
self.msl_popGestureRecognizer.delegate = [self msl_fullScreenPopGestureRecognizerDelegate];
[self.msl_popGestureRecognizer addTarget:internalTarget action:internalAction];
//这个手势是在屏幕边缘侧滑,如果有一些界面你不想添加滑动手势pop控制器的话,就设置为NO,
self.interactivePopGestureRecognizer.enabled = NO;
}
if (![self.viewControllers containsObject:viewController]) {
[self msl_pushViewController:viewController animated:YES];
}
}
在上述方法中,我们先判断负责popView的手势识别器中的View是否有我们自己定义的手势(第一次push,当然没有: ) ),这样我们就给它添加上手势. 单纯的添加上手势,系统又不知道这个手势触发时需要pop控制器.所以我们在下图的中,可以看到手势里面其实是有一个target-action的,是因为手势触发后,让_target来执行_action,从而进行pop控制器. 所以我们给自己的手势添加_target并且执行_action即可.下图即有如何找到_target和_action的步骤. 在方法的最后,我们进行了判断,当前的navigationController是否包含了要push的viewController(第一次push是没有的),所以,我们这里调用一下当前方法,会自动去执行系统的pushViewController...的实现,这样我们就完成了自己想要的额外操作.

在全屏返回手势中的gestureRecognizer,有一个成员变量是_targets,_targets是一个数组,数组中的每一个对象是一个UIGestureRecognizerTarget对象.该对象有一个_edgePanRecognizer成员变量,是一个UIScreenEdgePanGestureRecognizer对象. gestureRecognizer还有一个_action的方法,方法名是:"handleNavigationTransition:",所以我们在重写了pushViewController方法,需要给viewController添加一个全屏的拖拽手势,这个手势需要添加一个target&action,target就是_target,action就是tuo'zhuai"handleNavigationTransition:",这样就可以执行全屏拖拽手势了.
- (MSLFullScreenPopGestureRecognizerDelegate *)msl_fullScreenPopGestureRecognizerDelegate {
MSLFullScreenPopGestureRecognizerDelegate * delegate = objc_getAssociatedObject(self, _cmd);
if (!delegate) {
delegate = [[MSLFullScreenPopGestureRecognizerDelegate alloc] init];
delegate.navigationController = self;
objc_setAssociatedObject(self, _cmd, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return delegate;
}
上述方法就像一个懒加载,不过使用了runtime的关联方法.创建一个代理对象,并且返回.
- (UIPanGestureRecognizer *)msl_popGestureRecognizer {
UIPanGestureRecognizer * popGestureRecognizer = objc_getAssociatedObject(self, _cmd);
if (!popGestureRecognizer) {
popGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
popGestureRecognizer.maximumNumberOfTouches = 1;
objc_setAssociatedObject(self, _cmd, popGestureRecognizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return popGestureRecognizer;
}
因为msl_popGestureRecognizer是我们在分类中添加的属性,所以我们需要重写其getter方法,这里我们同样使用了runtime的关联对象.
整个UINavigationController的分类大致如此吧.
源码地址: https://github.com/colorfulOx/LRQPopGesture.git