导航控制器自定义过渡动画

时间:2014-10-26 03:22:51

标签: ios cocoa-touch custom-transition

我一直在关注一些教程,以便在从一个视图转换到另一个视图时创建自定义动画。

我使用来自here的自定义segue的测试项目运行正常,但有人告诉我,不再鼓励在自定义segue中进行自定义动画,我应该使用UIViewControllerAnimatedTransitioning

我遵循了几个使用此协议的教程,但所有教程都是关于模态演示(例如this tutorial)。

我想要做的是在导航控制器树中推送segue,但是当我尝试使用show(push)segue做同样的事情时,它不再起作用了。

请告诉我在导航控制器中自定义将动画从一个视图转换到另一个视图的方法。

无论如何我可以使用一种方法来制作所有过渡动画吗?如果有一天我想做同样的动画但最终不得不复制代码两次来处理模态与控制器的转换,那将会很尴尬。

4 个答案:

答案 0 :(得分:188)

要使用导航控制器(UINavigationController)进行自定义转换,您应该:

  • 定义视图控制器以符合UINavigationControllerDelegate协议。例如,您可以在视图控制器的.m文件中使用私有类扩展来指定与此协议的一致性:

    @interface ViewController () <UINavigationControllerDelegate>
    
    @end
    
  • 确保您实际将视图控制器指定为导航控制器的代理:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.navigationController.delegate = self;
    }
    
  • 在视图控制器中实施animationControllerForOperation

    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                      animationControllerForOperation:(UINavigationControllerOperation)operation
                                                   fromViewController:(UIViewController*)fromVC
                                                     toViewController:(UIViewController*)toVC
    {
        if (operation == UINavigationControllerOperationPush)
            return [[PushAnimator alloc] init];
    
        if (operation == UINavigationControllerOperationPop)
            return [[PopAnimator alloc] init];
    
        return nil;
    }
    
  • 实现推送和弹出动画的动画制作工具,例如:

    @interface PushAnimator : NSObject <UIViewControllerAnimatedTransitioning>
    
    @end
    
    @interface PopAnimator : NSObject <UIViewControllerAnimatedTransitioning>
    
    @end
    
    @implementation PushAnimator
    
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        return 0.5;
    }
    
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        UIViewController* toViewController   = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
        [[transitionContext containerView] addSubview:toViewController.view];
    
        toViewController.view.alpha = 0.0;
    
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            toViewController.view.alpha = 1.0;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
    }
    
    @end
    
    @implementation PopAnimator
    
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        return 0.5;
    }
    
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        UIViewController* toViewController   = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
        [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
    
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            fromViewController.view.alpha = 0.0;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
    }
    
    @end
    

    这确实会淡化过渡,但您可以根据需要随意自定义动画。

  • 如果你想处理交互式手势(例如从左到右弹出的本地滑动),你必须实现一个交互控制器:

    • 为交互控制器(符合UIViewControllerInteractiveTransitioning的对象)定义属性:

      @property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController;
      

      这个UIPercentDrivenInteractiveTransition是一个很好的对象,可以根据手势的完整程度来更新自定义动画。

    • 在您的视图中添加手势识别器。在这里,我只是实现左手势识别器来模拟一个流行音乐:

      UIScreenEdgePanGestureRecognizer *edge = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFromLeftEdge:)];
      edge.edges = UIRectEdgeLeft;
      [view addGestureRecognizer:edge];
      
    • 实现手势识别器处理程序:

      /** Handle swipe from left edge
       *
       * This is the "action" selector that is called when a left screen edge gesture recognizer starts.
       *
       * This will instantiate a UIPercentDrivenInteractiveTransition when the gesture starts,
       * update it as the gesture is "changed", and will finish and release it when the gesture
       * ends.
       *
       * @param   gesture       The screen edge pan gesture recognizer.
       */
      
      - (void)handleSwipeFromLeftEdge:(UIScreenEdgePanGestureRecognizer *)gesture {
          CGPoint translate = [gesture translationInView:gesture.view];
          CGFloat percent   = translate.x / gesture.view.bounds.size.width;
      
          if (gesture.state == UIGestureRecognizerStateBegan) {
              self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
              [self popViewControllerAnimated:TRUE];
          } else if (gesture.state == UIGestureRecognizerStateChanged) {
              [self.interactionController updateInteractiveTransition:percent];
          } else if (gesture.state == UIGestureRecognizerStateEnded) {
              CGPoint velocity = [gesture velocityInView:gesture.view];
              if (percent > 0.5 || velocity.x > 0) {
                  [self.interactionController finishInteractiveTransition];
              } else {
                  [self.interactionController cancelInteractiveTransition];
              }
              self.interactionController = nil;
          }
      }
      
    • 在导航控制器委托中,您还必须实施interactionControllerForAnimationController委托方法

      - (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                               interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
          return self.interactionController;
      }
      

如果你google&#34; UINavigationController自定义转换教程&#34;而且你会获得很多热门歌曲。或者查看WWDC 2013 Custom Transitions video

答案 1 :(得分:14)

您可能希望在addSubview

之前添加以下代码
  toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];

来自另一个问题custom-transition-for-push-animation-with-navigationcontroller-on-ios-9

来自Apple的finalFrameForViewController文档:

  

返回指定视图控制器的结束帧矩形   图。

     

此方法返回的矩形表示的大小   过渡结束时的相应视图。对于视图来说   在演示期间覆盖,此方法返回的值   可能是CGRectZero,但它也可能是一个有效的框架矩形。

答案 2 :(得分:4)

使用Rob&amp; amp;&amp;问:我的完美答案,这里是简化的Swift代码,使用.push和.pop相同的淡入淡出动画:

extension YourViewController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController,
                              animationControllerFor operation: UINavigationControllerOperation,
                              from fromVC: UIViewController,
                              to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

        //INFO: use UINavigationControllerOperation.push or UINavigationControllerOperation.pop to detect the 'direction' of the navigation

        class FadeAnimation: NSObject, UIViewControllerAnimatedTransitioning {
            func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
                return 0.5
            }

            func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
                let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
                if let vc = toViewController {
                    transitionContext.finalFrame(for: vc)
                    transitionContext.containerView.addSubview(vc.view)
                    vc.view.alpha = 0.0
                    UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
                    animations: {
                        vc.view.alpha = 1.0
                    },
                    completion: { finished in
                        transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
                    })
                } else {
                    NSLog("Oops! Something went wrong! 'ToView' controller is nill")
                }
            }
        }

        return FadeAnimation()
    }
}

不要忘记在YourViewController的viewDidLoad()方法中设置委托:

override func viewDidLoad() {
    //...
    self.navigationController?.delegate = self
    //...
}

答案 3 :(得分:3)

它适用于swift 3和4

    @IBAction func NextView(_ sender: UIButton) {
        let newVC = self.storyboard?.instantiateViewControllerWithIdentifier(withIdentifier: "NewVC") as! NewViewController

                let transition = CATransition()
                transition.duration = 0.5
                transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
                transition.type = kCATransitionPush
                transition.subtype = kCAGravityLeft
    //instead "kCAGravityLeft" try with different transition subtypes

        self.navigationController?.view.layer.add(transition, forKey: kCATransition)
        self.navigationController?.pushViewController(newVC, animated: false)

            }