zoukankan      html  css  js  c++  java
  • App开发流程之右滑返回手势功能续

    上一篇记录了利用系统私有变量和方法实现右滑返回手势功能:http://www.cnblogs.com/ALongWay/p/5893515.html

    这篇继续记录另一种方案:利用UINavigationController的delegate方法。

    核心代理方法有如下两个:

    - (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                              interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0);
    
    - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                       animationControllerForOperation:(UINavigationControllerOperation)operation
                                                    fromViewController:(UIViewController *)fromVC
                                                      toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);

    第一个代理方法,要求在视图控制器变换时候,返回一个实现UIViewControllerInteractiveTransitioning协议的对象,该对象用于控制交互进度和状态。

    UIKit.h/UIViewControllerTransitioning.h中已经准备好了一个这样的对象,名为UIPercentDrivenInteractiveTransition。

    主要关注其如下三个方法:

    - (void)updateInteractiveTransition:(CGFloat)percentComplete;//控制交互进度的百分比

    - (void)cancelInteractiveTransition;//中途取消交互

    - (void)finishInteractiveTransition;//完成交互

    可以在一个实现了代理方法的类中,定义一个手势目标方法来调用上述方法,达到控制作为变量的UIPercentDrivenInteractiveTransition对象的目的。

    主要代码如下:

    - (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
    {
        if ([animationController isKindOfClass:[PopAnimation class]])
            return _interactivePopTransition;
        
        return nil;
    }
    
    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
    {
        if (operation == UINavigationControllerOperationPop)
            return _popAnimation;
        
        return nil;
    }
    
    - (void)handlePopGestureOperation:(UIPanGestureRecognizer *)recognizer{
        //将手指在屏幕上的移动距离与屏幕宽度比例作为动画的进度
        CGPoint translationInScreen = [recognizer translationInView:[UIApplication sharedApplication].keyWindow];
        CGFloat progress = translationInScreen.x / [UIScreen mainScreen].bounds.size.width;
        progress = MIN(1.0, MAX(0.0, progress));
        
        if (recognizer.state == UIGestureRecognizerStateBegan) {
            //新建手势交互对象
            _interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
    
            //开始执行pop动画
            [_naviController popViewControllerAnimated:YES];
        }else if (recognizer.state == UIGestureRecognizerStateChanged) {
             //更新进度
            [_interactivePopTransition updateInteractiveTransition:progress];
        }else if (recognizer.state == UIGestureRecognizerStateEnded
                  || recognizer.state == UIGestureRecognizerStateCancelled) {
            //手势结束时如果进度大于一半,那么就完成pop操作,否则取消
            if (progress > 0.5) {
                [_interactivePopTransition finishInteractiveTransition];
            }else {
                [_interactivePopTransition cancelInteractiveTransition];
            }
            
            //手势交互结束,清理对象
            _interactivePopTransition = nil;
        }
    }

     

    第二个代理方法,要求在操作开始时候,返回一个实现UIViewControllerAnimatedTransitioning协议的对象,该对象实现了动画内容和执行时间。

    该协议有三个方法:

    // This is used for percent driven interactive transitions, as well as for container controllers that have companion animations that might need to
    // synchronize with the main animation. 
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
    // This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
    
    
    @optional
    
    // This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked.
    - (void)animationEnded:(BOOL) transitionCompleted;

    第一个方法返回动画时间;第二个方法,可以得到实现了UIViewControllerContextTransitioning协议的对象;第三个方法可选,如果transitionContext对象调用了completeTransition:方法,该代理方法会被调用。

    重点是第二个方法的transitionContext对象,其重要的方法如下:

    // The view in which the animated transition should take place.
    - (nullable UIView *)containerView;//动画所在的容器view
    - (BOOL)transitionWasCancelled;//转换是否取消// This must be called whenever a transition completes (or is cancelled.)
    // Typically this is called by the object conforming to the
    // UIViewControllerAnimatedTransitioning protocol that was vended by the transitioning
    // delegate.  For purely interactive transitions it should be called by the
    // interaction controller. This method effectively updates internal view
    // controller state at the end of the transition.
    - (void)completeTransition:(BOOL)didComplete;//完成转换,必须在动画完成时调用
    
    
    // Currently only two keys are defined by the
    // system - UITransitionContextToViewControllerKey, and
    // UITransitionContextFromViewControllerKey. 
    // Animators should not directly manipulate a view controller's views and should
    // use viewForKey: to get views instead.
    - (nullable __kindof UIViewController *)viewControllerForKey:(NSString *)key;//取得视图控制器
    
    // Currently only two keys are defined by the system -
    // UITransitionContextFromViewKey, and UITransitionContextToViewKey
    // viewForKey: may return nil which would indicate that the animator should not
    // manipulate the associated view controller's view.
    - (nullable __kindof UIView *)viewForKey:(NSString *)key NS_AVAILABLE_IOS(8_0);//取得视图控制器的view,兼容iOS7,可以直接用上面取到的viewController.view

    创建一个实现UIViewControllerAnimatedTransitioning协议的对象PopAnimation,重要代码如下:

    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
        return _transitionDuration;
    }
    
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
        _transitionContext = transitionContext;
    
        //当前控制器
        UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        
        //动画结束显示的控制器
        UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        
        //执行交互动画的容器view
        UIView *containerView = [transitionContext containerView];
        [containerView insertSubview:toViewController.view belowSubview:fromViewController.view];
        
        NSTimeInterval duration = [self transitionDuration:transitionContext];
        
        switch (_popAnimationType) {
            case PopAnimationTranslation: {
                CGFloat containerWidth = containerView.frame.size.width;
                toViewController.view.transform = CGAffineTransformMakeTranslation(-containerWidth / 4.0, 0);
    
                [UIView animateWithDuration:duration animations:^{
                    toViewController.view.transform = CGAffineTransformMakeTranslation(0, 0);
                    fromViewController.view.transform = CGAffineTransformMakeTranslation(containerWidth, 0);
                }completion:^(BOOL finished) {
                    //动画结束,必须调用此方法
                    [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
                }];
    
                break;
            }
            case PopAnimationFlip: {
                [UIView beginAnimations:@"View Flip" context:nil];
                [UIView setAnimationDuration:duration];
                [UIView setAnimationDelegate:self];
                [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:containerView cache:YES];
                [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:)];
                [UIView commitAnimations];
                [containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
    
                break;
            }
            case PopAnimationCube: {
                CATransition *transiton = [CATransition animation];
                transiton.type = @"cube";
                transiton.subtype = @"fromLeft";
                transiton.duration = duration;
                transiton.removedOnCompletion = NO;
                transiton.fillMode = kCAFillModeForwards;
                transiton.delegate = self;
                [containerView.layer addAnimation:transiton forKey:nil];
                [containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
    
                break;
            }
        }
    }
    
    - (void)animationDidStop:(CATransition *)anim finished:(BOOL)flag {
        [_transitionContext completeTransition:!_transitionContext.transitionWasCancelled];
    }

    剩下的工作:

    在上一篇的基础上,修改UINavigationController的base_pushViewController方法

    - (void)base_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
        if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.base_panGestureRecognizer]) {
            [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.base_panGestureRecognizer];
            
            self.base_panGestureRecognizer.delegate = [self base_panGestureRecognizerDelegateObject];
            
            self.base_currentDelegateObject = [[NavigationControllerDelegateObject alloc] initWithUINavigationController:self];
            
            [self.base_panGestureRecognizer addTarget:self.base_currentDelegateObject action:@selector(handlePopGestureOperation:)];
            
            self.interactivePopGestureRecognizer.enabled = NO;
        }
        
        [self base_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
        
        if (![self.viewControllers containsObject:viewController]) {
            [self base_pushViewController:viewController animated:animated];
        }
    }

    可以看到,去除了私有变量和方法的调用,自定义了名为NavigationControllerDelegateObject的对象,其实现了UINavigationControllerDelegate协议,并实现了pan手势的处理方法。在此,我暴露了该属性对象,考虑到以后可能会增加或者修改UINavigationControllerDelegate的实现协议,可以为该对象增加分类来达到目的;还有一个目的,便于修改popAnimation的动画类型和执行时间。

     

    总结:

    两篇记录,从不同的方向,记录了右滑返回手势功能的实现。利用系统变量和方法,方便快捷;利用协议方法,高度自定义。

    但是,利用协议方法,需要处理的方面比较多。虽然上述已经记录了实现核心,示例代码中也有详细代码,但是仍然还有很多方面未考虑。

    目前,我发现的一些问题:

    1.导航栏交互效果也需要自行实现,暂未实现原生效果

    2.导航栏由隐藏到显示的效果,有时会出现异常

    3.如果带有bottomBar,效果不理想

     

    如果有好的解决办法,希望告知,谢谢。

     

    base项目已更新:git@github.com:ALongWay/base.git
    项目增加了新的UINavigationController+PopOperation分类,但是未引用到项目中,需要的同学,可先删除PopGesture分类的引用,再添加该分类的应用。

  • 相关阅读:
    js刷新
    getHibernateTemplate()为NUll
    struts2+hibernate+spring+jquery返回json List列表
    windowopen
    web配置详解
    缓存
    uuid-不好之处
    多对多转化一对多
    多对多拆成两个 多对一
    我的github地址账号和密码
  • 原文地址:https://www.cnblogs.com/ALongWay/p/5896982.html
Copyright © 2011-2022 走看看