【问题标题】:Navigation controller custom transition animation导航控制器自定义过渡动画
【发布时间】:2014-12-21 13:33:17
【问题描述】:

我一直在关注一些教程来创建自定义动画,同时从一个视图过渡到另一个视图。

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

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

我正在尝试做的是导航控制器树中的推送转场,但是当我尝试使用显示(推)转场做同样的事情时,它不再起作用了。

请告诉我在导航控制器中自定义从一个视图到另一个视图的过渡动画的正确方法。

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

【问题讨论】:

  • @Rob Urgh 如果这听起来很愚蠢,我很抱歉,但是如何让我的视图控制器成为导航控制器的代表?我似乎无法将它们绑定到情节提要上,并且我的 'navigationController:animationControllerForOperation: fromViewController:toViewController:' 永远不会被调用。
  • 您可以在viewDidLoad 中执行self.navigationController.delegate = self;。除非您还在 @interface 行中指定您的视图控制器符合 <UINavigationControllerDelegate> 协议,否则您可能会收到警告。
  • 此代码是 swift 4 用于循环过渡。 stackoverflow.com/a/49321985/8334818

标签: ios cocoa-touch custom-transition


【解决方案1】:

要使用导航控制器 (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;
      }
      

如果你用谷歌搜索“UINavigationController 自定义过渡教程”,你会得到很多点击。或查看WWDC 2013 Custom Transitions video。

【讨论】:

  • 除此之外如何保留默认的向后滑动(屏幕边缘手势)?
  • @kidsid49 - 当您进行自定义转换时,您会失去内置的交互式弹出手势,但您也可以选择实现自己的交互控制器。有关更多信息,请参阅该 WWDC 视频。参考关于UIPercentDrivenInteractiveTransition的讨论(大大简化了流程)。我在上面添加了一些相关的代码sn-ps。
  • @Rob 感谢您提供如此详细的答案,我能够成功地将自定义动画添加到我的应用程序中。不过我确实有一个查询,我添加了一个自定义弹出动画,这样当用户从左向右滑动时,左侧的视图就会出现,就像没有自定义动画的普通弹出一样。问题是我似乎无法从左侧进入视图以完全跟随我的拇指/手指,在我触摸的位置和左视图边界的位置之间总是有一定的距离,我将如何制作这样视图边界就是我要触摸的地方?
  • 你如何触发这个推送/弹出转换。我尝试在情节提要中创建 segues(显示例如 Push),并使用 NavigationController 堆栈上 viewControllers 中的方法 performSegueWithIdentifier,委托以描述的方式处理转换,但它不起作用。那么如何开始这个过渡呢?
  • @Pavan - 您的animationControllerForOperation 可以检查fromVC 和/或toVC,如果您不希望它执行自定义动画,则返回nil。最简单的就是检查它是否是一个特定的类。更优雅的是,您可以设计一个视图控制器可以遵循的可选协议,并带有一些布尔属性来指示他们是否想要自定义推送/弹出。
【解决方案2】:

您可能想在addSubview之前添加以下代码

  toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];

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

来自 Apple 的 finalFrameForViewController 文档:

返回指定视图控制器的结束框矩形 查看。

此方法返回的矩形表示 转换结束时的相应视图。对于目前的看法 演示期间覆盖,此方法返回的值 可能是 CGRectZero 但它也可能是一个有效的框架矩形。

【讨论】:

  • 哇,这解决了我的问题。为什么这条信息似乎没有我看过的其他地方?像 Apple 文档、其他教程一样……对我来说,你必须设置它是没有意义的。
  • 重点是我正在使用自动布局并在其他地方设置框架......
【解决方案3】:

使用 Rob 和 Q i 的完美答案,这里是简化的 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
    //...
}

【讨论】:

    【解决方案4】:

    它适用于 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)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-04
      相关资源
      最近更新 更多