导航控制器默认自带了侧滑pop掉当前控制器的功能,但是只有在界面的左边拖动的时候才会触发侧滑POP的功能,也就是说手势触发的范围只能在左边的一小块,说明系统手势触发的方法实现了滑动返回功能。
我们创建手势对象的时候,需要绑定监听者
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(action)];
当用户在界面左边滑动,有滑动返回功能,这是因为触发手势了,调用target的action方法,说明方法内部实现滑动返回功能,否则就不会有滑动返回效果。
在导航控制器的ViewDidLoad方法中打印滑动手势
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",self.interactivePopGestureRecognizer);
NSLog(@"%@",self.interactivePopGestureRecognizer.delegate);
}
打印结果如下
<UIScreenEdgePanGestureRecognizer: 0x7f87b16a9360; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7f87b16a7ac0>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7f87b16aeb70>)>>
self.interactivePopGestureRecognizer.delegate =<_UINavigationInteractiveTransition: 0x7faf4a4356f0>
可以看出:
系统自带的手势是UIScreenEdgePanGestureRecognizer类型的对象 看名称就知道,这个手势的范围只能在屏幕的边缘
系统自带的手势的target 是_UINavigationInteractiveTransition类型的对象
系统调用的方法是handleNavigationTransition:
系统UIScreenEdgePanGestureRecognizer手势的代理就是_UINavigationInteractiveTransition
那么如何给自己的导航控制器添加1个全屏幕的滑动手势呢:
这里仅仅是提供一种思路 使用截图的方式来实现全屏滑动返回
为了方便演示 在工程中创建了1个有三个自控制器的导航控制器
在导航控制器的.h文件中
定义了一些宏
#import "GZDNavgationController.h"
#define kAnimatationDuration 0.25
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
#define kScreenHeight [UIScreen mainScreen].bounds.size.height
#define kScreenSize [UIScreen mainScreen].bounds.size
#define kScreenBounds [UIScreen mainScreen].bounds
声明了三个属性
@interface GZDNavgationController ()
//装有所有截图的数组
@property (strong,nonatomic) NSMutableArray *screenImages;
//最后1个截图图片的imageView
@property (strong,nonatomic) UIImageView *lastImageView;
//遮罩
@property (strong,nonatomic) UIView *coverView;
@end
首先进行懒加载
-(NSMutableArray *)screenImages {
if (!_screenImages) {
_screenImages = [NSMutableArray array];
}
return _screenImages;
}
//为了保证不创建太多的imageview,所以使用懒加载的方式
- (UIImageView *)lastImageView{
if (!_lastImageView) {
_lastImageView = [[UIImageView alloc] init];
}
return _lastImageView;
}
//遮罩的view
- (UIView *)coverView{
if (!_coverView) {
_coverView = [[UIView alloc] init];
_coverView.backgroundColor = [UIColor blackColor];
_coverView.alpha = 0.5;
_coverView.frame = kScreenBounds;
}
return _coverView;
}
在viewDidLoad 方法中创建平移手势
- (void)viewDidLoad {
[super viewDidLoad];
//创建平移手势
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
//将平移手势添加到导航控制器上
[self.view addGestureRecognizer:panGesture];
}
写了1个方法用来截取控制器全屏的图片
- (void)getScreenImage{
//截取上一个控制器的全屏的图片
//开启上下文
UIGraphicsBeginImageContextWithOptions(kScreenSize, YES, 0.0);
//将self.View渲染到图形上下文
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
//从图形上下文中获得图片
UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
//关闭图形上下文
UIGraphicsEndImageContext();
[self.screenImages addObject:screenShot];
}
由于第一张绿色控制器的的图片要在控制器显示的时候就截图下来,所以考虑在viewDidAppear 中截图添加到数组中
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//因为导航控制器有可能会消失,出现多次
//第一张图片只在数组没有元素的时候才截取,如果截图数组中有图片就不截取
if (self.screenImages.count > 0)return;
[self getScreenImage];
}
处理平移手势的方法
- (void)handlePanGesture:(UIPanGestureRecognizer *)pan{
//如果子控制器的个数小于等于1-->就是导航控制器里面只有根控制器在,此时是不需要手势,直接返回
if (self.viewControllers.count <= 1)return;
//手指在屏幕上水平方向移动的距离
CGFloat moveX = [pan translationInView:self.view].x;
//如果手指向左移动此时moveX <0 ,无需判断左移的情况 所以直接返回.
if (moveX < 0)return;
//能来到下面 说明满足手势触发的条件 即不是导航控制器的根控制器 同时手势是向右边滑动
//改变导航控制器的view的transform,使整个控制器都能够向左移动
self.view.transform = CGAffineTransformMakeTranslation(moveX, 0);
//将导航控制器的view平移操作之后下面是没有任何东西的,所以会显示为黑色.
//考虑将截图的view 添加到window 上面
//获取window对象
UIWindow *window = [UIApplication sharedApplication].keyWindow;
//取出数组中的最后1个image将其加载到懒加载的imageView中
self.lastImageView.image
= self.screenImages[self.screenImages.count - 2];
//设置frame
self.lastImageView.frame = kScreenBounds;
//在window上插入这个view
[window insertSubview:self.lastImageView atIndex:0];
//添加遮罩view 将遮罩的view 插入到lastImageView 的上面
[window insertSubview:self.coverView aboveSubview:self
.lastImageView];
//拿到self的view 的x
CGFloat x = self.view.frame.origin.x;
//如果手势的状态是停止的时候
//判断最终的停靠位置
if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) {
//如果平移的距离比屏幕宽度的一半还要大
if (x >= kScreenWidth * 0.5){
[UIView animateWithDuration:kAnimatationDuration animations:^{
self.view.transform = CGAffineTransformMakeTranslation(kScreenWidth, 0);
}completion:^(BOOL finished) {
//完成平移之后控制器出栈
[self popViewControllerAnimated:NO];
//导航控制器的view 被移动到最右边去了
self.view.transform = CGAffineTransformIdentity;
}];
}else{//如果平移的距离比屏幕宽度的一半要小
[UIView animateWithDuration:kAnimatationDuration animations:^{
//还原控制器
self.view.transform = CGAffineTransformIdentity;
}];
}
}
}
重写两个方法 拦截push 和pop的操作
//拦截push操作的方法 在这个方法里面截图
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
//在调用super使控制器入栈之后
[super pushViewController:viewController animated:animated];
//截图
[self getScreenImage];
}
//拦截pop操作的方法
- (UIViewController *)popViewControllerAnimated:(BOOL)animated{
//移除图片
[self.lastImageView removeFromSuperview];
//移除遮罩
[self.coverView removeFromSuperview];
//移除截图数组中的最后1个元素
[self.screenImages removeLastObject];
//调用父类pop方法出栈
return [super popViewControllerAnimated:animated];
}
最后实现的效果
可以看到在滑动栈顶控制器的时候 其实显示在下面的是1个全屏幕的截图而已...