导航控制器的全屏返回手势[截图]的实现思路

导航控制器默认自带了侧滑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个有三个自控制器的导航控制器

basic.gif

在导航控制器的.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];
}

最后实现的效果

完成.gif

可以看到在滑动栈顶控制器的时候 其实显示在下面的是1个全屏幕的截图而已...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容