美文网首页闻道丶iOS(大杂烩)
iOS关于右滑手势返回上一级

iOS关于右滑手势返回上一级

作者: 稻草人12138 | 来源:发表于2016-08-24 17:10 被阅读1667次

    首先iOS7以后系统默认自带了侧滑功能,当用户在界面的左边滑动的时候,就会有侧滑功能。

    但是如果我们从从导航控制器的返回按钮,就发现系统所带的侧滑返回功能无法使用,而且有些功能不尽人意.系统自定义的优点在于,当界面中有其它易冲突手势

    (像某控制器界面本身的轻扫或左滑右滑手势)时,系统滑动方法是边缘手势,与其它手势的作用区域可能会有不同,会有益于解决这些冲突。

    所以有以下自定义方法。

    1.全屏手势滑动

    - (void)viewDidLoad {

          [super viewDidLoad];

          // 获取系统自带滑动手势的target对象

          id target = self.interactivePopGestureRecognizer.delegate;

          // 创建全屏滑动手势,调用系统自带滑动手势的target的action方法

            UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];

        // 设置手势代理,拦截手势触发

         pan.delegate = self;

    // 给导航控制器的view添加全屏滑动手势

    [self.view addGestureRecognizer:pan];

    // 禁止使用系统自带的滑动手势

    self.interactivePopGestureRecognizer.enabled = NO;

    }

    // 什么时候调用:每次触发手势之前都会询问下代理,是否触发。

    // 作用:拦截手势触发

    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

    {

    // 注意:只有非根控制器才有滑动返回功能,根控制器没有。

    // 判断导航控制器是否只有一个子控制器,如果只有一个子控制器,肯定是根控制器

    if (self.childViewControllers.count == 1) {

    // 表示用户在根控制器界面,就不需要触发滑动手势,

    return NO;

    }

    return YES;

    }

    该方法实现了全屏手势滑动,但是,当当前视图有其它手势时可能产生冲突。


    2.部分视图没有右滑返回

    @property (nonatomic,strong) UIViewController *currentShowVC;

    -(void)viewWillAppear:(BOOL)animated

    {

    self.navigationController.navigationBarHidden=YES;

    [_firstVC hidenLabel];

    //设置代理

    self.navigationController.interactivePopGestureRecognizer.delegate =(id)self;

    //启用系统自带的滑动手势

    self.navigationController.interactivePopGestureRecognizer.enabled = YES;

    //判断当在视图栈的第几个是不用手势返回,一般为1,我这里因为有一个空白的跟视图所以设置为2

    if (self.navigationController.viewControllers.count == 2){

    //将当前导航控制器置空

    self.currentShowVC = Nil;

    }else{

    self.currentShowVC = self;

    }

    }

    -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

    {

    if (gestureRecognizer == self.navigationController.interactivePopGestureRecognizer) {

    //当前导航控制器是根视图控制器

    //the most important

    return (self.currentShowVC == self.navigationController.topViewController);

    }

    return YES;

    }


    关于部分界面没有返回手势:

    1.在当前界面的

    -(void)viewDidAppear:(BOOL)animated

    {

    self.navigationController.interactivePopGestureRecognizer.enabled = NO;

    }

    2.在下一个需要用到返回手势的界面

    -(void)viewWillAppear:(BOOL)animated

    {

    //设置代理

    self.navigationController.interactivePopGestureRecognizer.delegate =(id)self;

    //启用系统自带的滑动手势

    self.navigationController.interactivePopGestureRecognizer.enabled = YES;

    }

    3.自定义返回动画

    .h文件

    #import

    @interface AnimatedNavigationController : UINavigationController

    @end

    .m文件

    #import "AnimatedNavigationController.h"

    @interface AnimatedNavigationController ()

    @end

    #import "ViewController.h"

    @interface AnimatedNavigationController ()

    {

    // 屏幕截图

    UIImageView *_screenshotImgView;

    // 截图上面的黑色半透明遮罩

    UIView *_coverView;

    // 存放所有截图

    NSMutableArray *_screenshotImgs;

    }

    @end

    @implementation AnimatedNavigationController

    - (void)viewDidLoad

    {

    [super viewDidLoad];

    // 1,创建Pan手势识别器,并绑定监听方法

    UIPanGestureRecognizer *panGestureRec = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRec:)];

    // 为导航控制器的view添加Pan手势识别器

    [self.view addGestureRecognizer:panGestureRec];

    // 2.创建截图的ImageView

    _screenshotImgView = [[UIImageView alloc] init];

    // app的frame是除去了状态栏高度的frame

    _screenshotImgView.frame = [UIScreen mainScreen].applicationFrame;

    //(0 20; 320 460);

    // 3.创建截图上面的黑色半透明遮罩

    _coverView = [[UIView alloc] init];

    // 遮罩的frame就是截图的frame

    _coverView.frame = _screenshotImgView.frame;

    // 遮罩为黑色

    _coverView.backgroundColor = [UIColor blackColor];

    // 4.存放所有的截图数组初始化

    _screenshotImgs = [NSMutableArray array];

    }

    // 重写push方法,在push之前 先截取图片

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

    {

    // 只有在导航控制器里面有子控制器的时候才需要截图

    if (self.viewControllers.count >= 1) {

    // 调用自定义方法,使用上下文截图

    [self screenShot];

    }

    // 截图完毕之后,才调用父类的push方法

    [super pushViewController:viewController animated:YES];

    }

    // 使用上下文截图,并使用指定的区域裁剪,模板代码

    - (void)screenShot

    {

    // 将要被截图的view,即窗口的根控制器的view(必须不含状态栏,默认ios7中控制器是包含了状态栏的)

    UIViewController *beyondVC = (UIViewController *)self.view.window.rootViewController;

    // 背景图片 总的大小

    CGSize size = beyondVC.view.frame.size;

    // 开启上下文,使用参数之后,截出来的是原图(YES  0.0 质量高)

    UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);

    // 要裁剪的矩形范围

    CGRect rect = CGRectMake(0, -20.8, size.width, size.height + 20 );

    //注:iOS7以后renderInContext:由drawViewHierarchyInRect:afterScreenUpdates:替代

    [beyondVC.view drawViewHierarchyInRect:rect  afterScreenUpdates:NO];

    // 从上下文中,取出UIImage

    UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();

    // 添加截取好的图片到图片数组

    [_screenshotImgs addObject:snapshot];

    // 千万记得,结束上下文(移除栈顶的基于当前位图的图形上下文)

    UIGraphicsEndImageContext();

    }

    // 监听手势的方法,只要是有手势就会执行

    - (void)panGestureRec:(UIPanGestureRecognizer *)panGestureRec

    {

    // 如果当前显示的控制器已经是根控制器了,不需要做任何切换动画,直接返回

    if(self.topViewController == self.viewControllers[0]) return;

    // 判断pan手势的各个阶段

    switch (panGestureRec.state) {

    case UIGestureRecognizerStateBegan:

    // 开始拖拽阶段

    [self dragBegin];

    break;

    case UIGestureRecognizerStateEnded:

    // 结束拖拽阶段

    [self dragEnd];

    break;

    default:

    // 正在拖拽阶段

    [self dragging:panGestureRec];

    break;

    }

    }

    #pragma mark 开始拖动,添加图片和遮罩

    - (void)dragBegin

    {

    // 重点,每次开始Pan手势时,都要添加截图imageview 和 遮盖cover到window中

    [self.view.window insertSubview:_screenshotImgView atIndex:0];

    [self.view.window insertSubview:_coverView aboveSubview:_screenshotImgView];

    // 并且,让imgView显示截图数组中的最后(最新)一张截图

    _screenshotImgView.image = [_screenshotImgs lastObject];

    }

    // 默认的将要进行缩放的截图的初始比例

    #define kDefaultScale 0.6

    // 默认的将要变透明的遮罩的初始透明度(全黑)

    #define kDefaultAlpha 1.0

    // 当拖动的距离,占了屏幕的总宽高的3/4时, 就让imageview完全显示,遮盖完全消失

    #define kTargetTranslateScale 0.75

    #pragma mark 正在拖动,动画效果的精髓,进行缩放和透明度变化

    - (void)dragging:(UIPanGestureRecognizer *)pan

    {

    // 得到手指拖动的位移

    CGFloat offsetX = [pan translationInView:self.view].x;

    // 只允许往右边拖,禁止向左拖

    if (offsetX < 0) offsetX = 0;

    // 让整个view都平移     // 挪动整个导航view

    self.view.transform = CGAffineTransformMakeTranslation(offsetX, 0);

    // 计算目前手指拖动位移占屏幕总的宽高的比例,当这个比例达到3/4时, 就让imageview完全显示,遮盖完全消失

    double currentTranslateScaleX = offsetX/self.view.frame.size.width;

    // 让imageview缩放,默认的比例+(当前平衡比例/目标平衡比例)*(1-默认的比例)

    double scale = kDefaultScale + (currentTranslateScaleX/kTargetTranslateScale) * (1 - kDefaultScale);

    // 已经达到原始大小了,就可以了,不用放得更大了

    if (scale > 1) scale = 1;

    _screenshotImgView.transform = CGAffineTransformMakeScale(scale, scale);

    // 让遮盖透明度改变,直到减为0,让遮罩完全透明,默认的比例-(当前平衡比例/目标平衡比例)*默认的比例

    double alpha = kDefaultAlpha - (currentTranslateScaleX/kTargetTranslateScale) * kDefaultAlpha;

    _coverView.alpha = alpha;

    }

    #pragma mark 结束拖动,判断结束时拖动的距离作相应的处理,并将图片和遮罩从父控件上移除

    - (void)dragEnd

    {

    // 取出挪动的距离

    CGFloat translateX = self.view.transform.tx;

    // 取出宽度

    CGFloat width = self.view.frame.size.width;

    if (translateX <= width * 0.5) {

    // 如果手指移动的距离还不到屏幕的一半,往左边挪 (弹回)

    [UIView animateWithDuration:0.3 animations:^{

    // 重要~~让被右移的view弹回归位,只要清空transform即可办到

    self.view.transform = CGAffineTransformIdentity;

    // 让imageView大小恢复默认的scale

    _screenshotImgView.transform = CGAffineTransformMakeScale(kDefaultScale, kDefaultScale);

    // 让遮盖的透明度恢复默认的alpha 1.0

    _coverView.alpha = kDefaultAlpha;

    } completion:^(BOOL finished) {

    // 重要,动画完成之后,每次都要记得 移除两个view,下次开始拖动时,再添加进来

    [_screenshotImgView removeFromSuperview];

    [_coverView removeFromSuperview];

    }];

    } else {

    // 如果手指移动的距离还超过了屏幕的一半,往右边挪

    [UIView animateWithDuration:0.3 animations:^{

    // 让被右移的view完全挪到屏幕的最右边,结束之后,还要记得清空view的transform

    self.view.transform = CGAffineTransformMakeTranslation(width, 0);

    // 让imageView缩放置为1

    _screenshotImgView.transform = CGAffineTransformMakeScale(1, 1);

    // 让遮盖alpha变为0,变得完全透明

    _coverView.alpha = 0;

    } completion:^(BOOL finished) {

    // 重要~~让被右移的view完全挪到屏幕的最右边,结束之后,还要记得清空view的transform,不然下次再次开始drag时会出问题,因为view的transform没有归零

    self.view.transform = CGAffineTransformIdentity;

    // 移除两个view,下次开始拖动时,再加回来

    [_screenshotImgView removeFromSuperview];

    [_coverView removeFromSuperview];

    // 执行正常的Pop操作:移除栈顶控制器,让真正的前一个控制器成为导航控制器的栈顶控制器

    [self popViewControllerAnimated:NO];

    // 重要~记得这时候,可以移除截图数组里面最后一张没用的截图了

    [_screenshotImgs removeLastObject];

    }];

    }

    }

    @end

    demo地址: https://github.com/hyf12138/NavigationDemo.git

    相关文章

      网友评论

        本文标题:iOS关于右滑手势返回上一级

        本文链接:https://www.haomeiwen.com/subject/ghvfkttx.html