导航控制器自定义转换animation

我一直在遵循一些教程来创build自定义animation,同时从一个视图转换到另一个视图。

我从这里使用自定义segue的testing项目工作正常,但有人告诉我,不再鼓励自定义赛格内自定义animation,我应该使用UIViewControllerAnimatedTransitioning

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

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

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

无论如何,我可以使用所有过渡animation的一种方法吗? 如果有一天我想要做相同的animation,但是最终不得不复制代码两次,才能在模态与控制器之间进行转换,那将会很尴尬。

要使用导航控制器( 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; } 
  • 推动推动和stream行animation的animation师,例如:

     @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 

    这会淡化过渡,但是您应该随心所欲地自定义animation。

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

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

       @property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController; 

      这个UIPercentDrivenInteractiveTransition是一个很好的对象,根据手势的完整性来完成更新自定义animation。

    • 添加一个手势识别器到你的视图。 在这里,我只是实现左手势识别器来模拟stream行:

       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自定义转换video 。

你可能想在addSubview之前添加下面的代码

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

从另一个问题的自定义转换推push-animation-with-navigationcontroller-on-ios-9

从Apple的FinalFrameForViewController的文档:

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

此方法返回的矩形代表转换结束时相应视图的大小。 对于在演示期间被覆盖的视图,此方法返回的值可能是CGRectZero,但也可能是有效的框架矩形。

使用Rob的&Q我完美的答案,这里是简化的Swift代码,使用相同的淡出animation为.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 //... }