iOS使其支持侧滑返回

作者: Wang66 | 来源:发表于2015-12-05 00:35 被阅读19547次

    方案一 :

    开启使用系统自带的侧滑返回

    iOS7之后系统提供了侧滑手势(interactivePopGestureRecognizer),即从屏幕左侧边缘滑起会pop回导航控制器栈的上个viewController。不过如果你自定义了返回按钮,系统自带的侧滑返回功能会失效。此时需要添加下面的代码解决:

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

    缺点:

    • 必须从屏幕边缘左侧滑起才会触发;
    • 一旦自定义导航控制器或者自定义返回按钮,就会失效。

    方案二

    实现UINavigationViewController的代理方法,自定义动画对象和交互对象。(即自定义转场动画)

    这是苹果官方在WWDC上提倡的方法,灵活性高。可以高度自定义push和pop转场动画。
    这种方法需要我们彻底实现侧滑返回,那我们的思路就是:

    • 先给view添加一个UIPanGestureRecognizer手势;
    • 再自定义该手势的触发方法,该方法里实现了侧滑。
    1. 先创建一个BaseViewController,给该控制器的view添加拖动手势;
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.navigationController.delegate = self; // 设置navigationController的代理为self,并实现其代理方法
        
        self.view.userInteractionEnabled = YES;
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(backHandle:)];
        [self.view addGestureRecognizer:panGesture];
    }
    
    
    - (void)backHandle:(UIPanGestureRecognizer *)recognizer
    {
        [self customControllerPopHandle:recognizer];
    }
    
    2.侧滑手势会触发这个回调方法;
    - (void)customControllerPopHandle:(UIPanGestureRecognizer *)recognizer
    {
        if(self.navigationController.childViewControllers.count == 1)
        {
            return;
        }
        // _interactiveTransition就是代理方法2返回的交互对象,我们需要更新它的进度来控制POP动画的流程。(以手指在视图中的位置与屏幕宽度的比例作为进度)
        CGFloat process = [recognizer translationInView:self.view].x/self.view.bounds.size.width;
        process = MIN(1.0, MAX(0.0, process));
        
        if(recognizer.state == UIGestureRecognizerStateBegan)
        {
            // 此时,创建一个UIPercentDrivenInteractiveTransition交互对象,来控制整个过程中动画的状态
            _interactiveTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
            [self.navigationController popViewControllerAnimated:YES];
        }
        else if(recognizer.state == UIGestureRecognizerStateChanged)
        {
            [_interactiveTransition updateInteractiveTransition:process]; // 更新手势完成度
        }
        else if(recognizer.state == UIGestureRecognizerStateEnded ||recognizer.state == UIGestureRecognizerStateCancelled)
        {
            // 手势结束时,若进度大于0.5就完成pop动画,否则取消
            if(process > 0.5)
            {
                [_interactiveTransition finishInteractiveTransition];
            }
            else
            {
                [_interactiveTransition cancelInteractiveTransition];
            }
            
            _interactiveTransition = nil;
        }
    }
    
    3.实现UINavigationControllerDelegate的两个协议方法,分别返回自定义动画需要的两个重要对象;
    // 代理方法1:
    // 返回一个实现了UIViewControllerAnimatedTransitioning协议的对象    ,即完成转场动画的对象
    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
    {
        if(operation == UINavigationControllerOperationPop) // 若operation是pop,就返回我们自定义的转场动画对象
        {
            return [[POPAnimation alloc] init];
        }
        
        return nil;
    }
    
    
    // 代理方法2
    // 返回一个实现了UIViewControllerInteractiveTransitioning协议的对象,即完成动画交互(动画进度)的对象
    - (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
    {
        if([animationController isKindOfClass:[POPAnimation class]])
        {
            return _interactiveTransition;
        }
        return nil;
    }
    
    4.创建一个自定义动画类:POPAnimation。这是自定义动画的核心
    • 自定义动画类,即一个实现了UIViewControllerAnimatedTransitioning协议的类。
    • 实现该协议的两个方法,一个返回动画执行时间,一个自定义动画。
    #import "POPAnimation.h"
    
    
    @interface POPAnimation ()
    
    @end
    
    @implementation POPAnimation
    
    // 实现两个协议的方法
    
    // 返回动画执行的时间
    - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        return 0.25;
    }
    
    
    //
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        __block UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; // 动画来自哪个vc
        UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; // 转场到哪个vc
        
        // 转场动画是两个控制器视图的动画,需要一个containerView作为“舞台”
        UIView *containerView = [transitionContext containerView];
        [containerView insertSubview:toVC.view belowSubview:fromVC.view];
        
        NSTimeInterval duration = [self transitionDuration:transitionContext]; // 获取动画执行时间(实现的协议方法)
        
        // 执行动画,让fromVC的view移动到屏幕最右侧
        [UIView animateWithDuration:duration animations:^{
            fromVC.view.transform = CGAffineTransformMakeTranslation([UIScreen mainScreen].bounds.size.width, 0);
        } completion:^(BOOL finished) {
            // 当动画执行完时,这个方法必须要调用,否则系统会认为你的其余操作都在动画执行过程中
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
        
    }
    
    
    @end
    
    pop.gif

    方案三

    极其简单取巧的方法

    iOS7之后是有侧滑返回手势功能的。注意,也就是说系统已经定义了一种手势,并且给这个手势已经添加了一个触发方法(重点)。但是,系统的这个手势的触发条件是必须从屏幕左边缘开始滑动。我们取巧的方法是自己写一个支持全屏滑动的手势,而其触发方法系统已经有,没必要自己实现pop的动画,所以直接就把系统的触发处理方法作为我们自己定义的手势的处理方法。

    #import "ViewController.h"
    
    @interface ViewController ()<UIGestureRecognizerDelegate>
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        id target = self.navigationController.interactivePopGestureRecognizer.delegate;
        
        // handleNavigationTransition:为系统私有API,即系统自带侧滑手势的回调方法,我们在自己的手势上直接用它的回调方法
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];
        panGesture.delegate = self; // 设置手势代理,拦截手势触发
        [self.view addGestureRecognizer:panGesture];
        
        // 一定要禁止系统自带的滑动手势
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
    
    
    // 什么时候调用,每次触发手势之前都会询问下代理方法,是否触发
    // 作用:拦截手势触发
    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
    {
        // 当当前控制器是根控制器时,不可以侧滑返回,所以不能使其触发手势
        if(self.navigationController.childViewControllers.count == 1)
        {
            return NO;
        }
        
        return YES;
    }
    
    
    @end
    

    拓展

    方案二涉及到了自定义转场动画的东西。关于自定义转场动画有三个核心的协议:

    • UIViewControllerAnimatedTransitioning:遵从该协议的对象,即是我们自定义的动画;
    • UIViewControllerInteractiveTransitioning:遵从该协议实现动画可交互性。不过一般我们直接使用系统UIPercentDrivenInteractiveTransition类,不需自定义。
    • UIViewControllerContextTransitioning:遵从该协议,定义了转场时需要的元数据。一般不需自己定义。

    相关文章

      网友评论

      • 054ce4d15908:能不能让导航栏跟着界面整体返回 , 如果前一个界面没有导航栏侧滑返回的时候, 就没有导航栏了 很丑
        4ebd810247c1:⚠️注意设置animated
        [self.navigationController setNavigationBarHidden:shouldHidden animated:YES];
        不要直接self.navigationController.navigationBar.hidden = YES;
      • 阿凡提说AI:方案一的那句代码是在每个push出的控制器都要写吗?
        JaneEyre3X:我也想问这个呢,我用了第一个之后,有一个自定义了一个颜色,一侧滑返回颜色就没有了 不知道什么原因
      • Tomous:POPAnimation.h继承的是一个view吗
      • 多LV信源:方案一中的, 自定义了UINavigationViewControlle, 指的是什么?
        Wang66:@多LV信源 应该是NavBar,自定义它有时会导致侧滑手势失效
        多LV信源:在线等答复
        多LV信源:工程中自定义了基NavVC, 没有自定义返回按钮, 也不见系统自带的侧滑返回效果消失, 是不是写得有误?
      • 超级码LEO:当页面tableview上下滑动的时候 还可以触发侧滑返回 怎么样在tableview上下滑动的时候侧滑无效呢 ?
      • 6cddee706b65:你好,我用方案三中:
        // handleNavigationTransition:为系统私有API,即系统自带侧滑手势的回调方法,我们在自己的手势上直接用它的回调方法
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];

        这段代码时,因为没有handleNavigationTransition这个方法,会报警告,有什么好的建议吗?我写个方法名为handleNavigationTransition,方法内什么都不写的话会有影响吗?
        超级码LEO:当页面tableview上下滑动的时候 还可以触发侧滑返回 怎么样在tableview上下滑动的时候侧滑无效呢 ?
        Teehom:方法三,这种还要判断手势的方向吧,你这样的太粗糙了
        我本善良:如果只是让编译器忽略警告的话可以用下面方法试一试
        忽略performSelector警告

        #pragma clang diagnostic push

        #pragma clang diagnostic ignored "-Warc-performSelector-leaks"

        [viewController performSelector:finishMethod withObject:request];

        #pragma clang diagnostic pop
      • iDog:老铁 我用的方案三 很好用 直到今天我写button的holdDownDragOutside事件 发现和手势冲突了 不知道老铁有没有什么好的解决方案
      • 奋斗吧_程序猿:方法一,会出现界面卡死的情况
        多LV信源:原因不知道, 但网上有解决的链接
        多LV信源:@我本善良 原因我不知道, 但知道怎么解决
        我本善良:我的也是偶现,这个原因是啥啊?
      • 超_iOS:方案三如果自定义返回按钮了,应该不用禁用左滑了吧?
      • 骑毛驴的小强:我自己写了一个用截图做的侧滑手势,目前来没说没有发现bug,看了你这个好高大上啊,666666
      • a24df6838a47:连续pop两次就出问题了
      • 花无名:方法二有demo地址么,求~
        Wang66:@爱玩爱笑的布丁 抱歉,时间久远,没demo了
      • a24df6838a47:nice 很好 棒棒哒
      • c2f7ad02c977:方法3会有审核问题么...
        Wang66:@CareGD 不会,可以通过的
      • Touchs:能不能让导航栏跟着界面整体返回 , 如果前一个界面没有导航栏侧滑返回的时候, 就没有导航栏了 很丑
        054ce4d15908:您解决了没啊,我也遇到同样的问题啦
      • Veer_Pan:好东西,给力啊
        Wang66:@veer_pan 是会冲突的。苹果的侧滑返回之所以设计成必须从屏幕左侧边缘滑起可能就是为了避免和屏幕上其他左滑动作冲突。当自己的产品设计成屏幕上有多个左滑动作时,你可以屏蔽不必要的一种。比如,我遇到过某个界面是地图,地图是可以左右滑动调整视野范围的,但是和自定义的侧滑返回冲突了,这时我把侧滑返回屏蔽了,只保留地图滑动。
        Veer_Pan:@Wang66 方法三会导致cell的侧滑事件失效,方式二应该类似,可以请教下你吗我qq673122409
        Wang66:@veer_pan 谢谢啊😄
      • 75724f2f1287:您好, 有幸能读到您的这篇文章, 我照着第二个方法完成了全屏侧滑返回, 但是我发现个 BUG, 在侧滑返回的时候, view 并不是跟着手指滑动的进度更新动画的.不同步.
        手指侧滑 0% ~ 25%的位置的时候, 动画比手指的速度慢
        25% ~ 50%, 这时候动画比手指快,50%时手指和动画同步了
        50% ~ 75%, 也是动画比手指速度快,
        75% ~ 100 %, 这个时候动画又慢了下来.
        总的来说就是看起来不协调, 不知道您有没有解决这个问题,如果可以麻烦您能回复告知一下, 谢谢~
        a24df6838a47:@王_胖胖 怎么减??
        a24df6838a47:@王_胖胖 怎么减呀 。。 我也遇到这个问题了
        王_胖胖:@左手键盘右手诗 滑动速率要减去之前的,不然越来越快,不知道我表达你能看懂不
      • Dean麦兜:侧滑返回有回调么,怎么知道侧滑到了能返回的状态了?
        Wang66:@Dean麦兜 方案一三都用的系统侧滑返回,触发条件是手势,达到触发手势的条件就回调给手势添加的回调方法。方案二中可通过手势当前滑动的尺度来限制是否完成“交互动画”。上面代码中是当滑动超过半屏时就完成交互动画,随机触发侧滑返回。

      本文标题:iOS使其支持侧滑返回

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