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

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

我的测试项目使用自定义接口从 给你工程很好,但有人告诉我,它不再鼓励做自定义动画在一个自定义接口,我应该使用 UIViewControllerAnimatedTransitioning

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

我试图做的是在导航控制器树中进行一个按钮切换,但是当我试图对 show (push)切换进行同样的操作时,它就不再起作用了。

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

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

49112 次浏览

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

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

    @interface ViewController () <UINavigationControllerDelegate>
    
    
    @end
    
  • Make sure you actually specify your view controller as your navigation controller's delegate:

    - (void)viewDidLoad {
    [super viewDidLoad];
    
    
    self.navigationController.delegate = self;
    }
    
  • Implement animationControllerForOperation in your view controller:

    - (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;
    }
    
  • Implement animators for push and pop animations, e.g.:

    @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];
      
    • Implement the gesture recognizer handler:

      /** 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;
      }
      }
      
    • In your navigation controller delegate, you also have to implement interactionControllerForAnimationController delegate method

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

If you google "UINavigationController custom transition tutorial" and you'll get many hits. Or see WWDC 2013 Custom Transitions video.

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

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

来自另一个问题 自定义过渡为推动动画与导航控制器上的 ios-9

来自苹果的 finalFrameForViewController 文档:

返回指定视图控制器的 风景。

此方法返回的矩形表示 转换结束时对应的视图 在表示过程中,此方法返回的值 可能是 CGRectZero,但也可能是一个有效的框架矩形。

使用 Rob 的 & Q i 的完美答案,这里是简化的 Swift 代码,使用相同的淡入动画。用力。流行音乐:

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和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)
}