web-dev-qa-db-ja.com

Navigation Controllerカスタムトランジションアニメーション

あるビューから別のビューに移行するときにカスタムアニメーションを作成するために、いくつかのチュートリアルに従っています。

here のカスタムセグエを使用した私のテストプロジェクトは正常に動作しますが、カスタムセグエ内でカスタムアニメーションを実行することはもう推奨されないので、UIViewControllerAnimatedTransitioningを使用する必要があります。

このプロトコルを使用するいくつかのチュートリアルに従いましたが、それらはすべてモーダルプレゼンテーションに関するものです(たとえば このチュートリアル )。

私がやろうとしているのは、Navigation Controllerツリー内のプッシュセグエですが、ショー(プッシュ)セグエで同じことをしようとすると、それはもう機能しません。

Navigation Controllerで、あるビューから別のビューへカスタムアニメーションを移行する正しい方法を教えてください。

とにかく、すべてのトランジションアニメーションに1つの方法を使用できますか?ある日、同じアニメーションを実行したいが、モーダルとコントローラーの移行で作業するためにコードを2回複製しなければならない場合は厄介です。

72
AVAVT

Navigation Controller(UINavigationController)を使用してカスタム移行を行うには、次のことを行う必要があります。

  • UINavigationControllerDelegateプロトコルに準拠するようにView Controllerを定義します。たとえば、View Controllerの.mファイルに、このプロトコルへの準拠を指定するプライベートクラス拡張を含めることができます。

    @interface ViewController () <UINavigationControllerDelegate>
    
    @end
    
  • 実際にView ControllerをNavigation Controllerのデリゲートとして指定していることを確認してください。

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.navigationController.delegate = self;
    }
    
  • View Controllerに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は、ジェスチャの完成度に基づいてカスタムアニメーションを更新するという面倒な作業を行うNiceオブジェクトです。

    • ビューにジェスチャレコグナイザーを追加します。ここでは、ポップをシミュレートするために左ジェスチャレコグナイザを実装しています。

      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;
          }
      }
      
    • Navigation Controllerのデリゲートでは、interactionControllerForAnimationControllerデリゲートメソッドも実装する必要があります

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

「UINavigationControllerカスタム遷移チュートリアル」をグーグルで検索すると、多くのヒットが得られます。または WWDC 2013 Custom Transitions video をご覧ください。

190
Rob

addSubviewの前に次のコードを追加できます

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

別の質問から custom-transition-for-Push-animation-with-navigationcontroller-on-ios-9

FinalFrameForViewControllerに関するAppleのドキュメントから:

指定されたView Controllerのビューの終了フレーム長方形を返します。

このメソッドによって返される長方形は、トランジションの最後の対応するビューのサイズを表します。プレゼンテーション中にカバーされるビューの場合、このメソッドによって返される値はCGRectZeroである可能性がありますが、有効なフレーム長方形である可能性もあります。

14
Q i

Rob&Q iの完璧な答えを使用して、.Pushと.popに同じフェードアニメーションを使用した簡略化された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
    //...
}
5
Edmund Elmer

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)

            }
3
Sai kumar Reddy