web-dev-qa-db-fra.com

Animation de transition personnalisée du contrôleur de navigation

J'ai suivi des tutoriels pour créer une animation personnalisée tout en passant d'une vue à une autre.

Mon projet de test utilisant la séquence personnalisée de ici fonctionne bien, mais quelqu'un m'a dit que ce n'était plus encouragé de créer une animation personnalisée dans une séquence personnalisée et que je devrais utiliser UIViewControllerAnimatedTransitioning.

J'ai suivi plusieurs tutoriels utilisant ce protocole, mais ils concernent tous une présentation modale (par exemple ce tutoriel ).

Ce que j'essaie de faire, c'est une séquence Push dans un arbre de contrôleur de navigation, mais lorsque j'essaie de faire la même chose avec une séquence Show (Push), cela ne fonctionne plus.

Dites-moi s'il vous plaît la bonne façon de faire une animation de transition personnalisée d'une vue à une autre dans un contrôleur de navigation.

Et puis-je quand même utiliser une méthode pour toutes les animations de transition? Ce serait gênant si un jour je veux faire la même animation mais que je finisse par devoir dupliquer le code deux fois pour travailler sur la transition modale par rapport au contrôleur.

72
AVAVT

Pour effectuer une transition personnalisée avec le contrôleur de navigation (UINavigationController), vous devez:

  • Définissez votre contrôleur de vue pour qu'il soit conforme au protocole UINavigationControllerDelegate. Par exemple, vous pouvez avoir une extension de classe privée dans le fichier .m De votre contrôleur de vue spécifiant la conformité à ce protocole:

    @interface ViewController () <UINavigationControllerDelegate>
    
    @end
    
  • Assurez-vous de bien spécifier votre contrôleur de vue en tant que délégué de votre contrôleur de navigation:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.navigationController.delegate = self;
    }
    
  • Implémentez animationControllerForOperation dans votre contrôleur de vue:

    - (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;
    }
    
  • Implémenter des animateurs pour les animations Push et Pop, par exemple:

    @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
    

    Cela fait disparaître la transition, mais vous devriez vous sentir libre de personnaliser l'animation comme bon vous semble.

  • Si vous souhaitez gérer les gestes interactifs (par exemple, un glissement natif de gauche à droite), vous devez implémenter un contrôleur d'interaction:

    • Définissez une propriété pour un contrôleur d'interaction (un objet conforme à UIViewControllerInteractiveTransitioning):

      @property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController;
      

      This UIPercentDrivenInteractiveTransition est un objet Nice qui effectue la mise à jour de votre animation personnalisée en fonction de la complétude du geste.

    • Ajoutez un identificateur de geste à votre vue. Ici, je suis juste en train d'implémenter le logiciel de reconnaissance de geste gauche pour simuler un pop:

      UIScreenEdgePanGestureRecognizer *Edge = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFromLeftEdge:)];
      Edge.edges = UIRectEdgeLeft;
      [view addGestureRecognizer:Edge];
      
    • Implémentez le gestionnaire de reconnaissance de gestes:

      /** 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;
          }
      }
      
    • Dans votre délégué de contrôleur de navigation, vous devez également implémenter la méthode interactionControllerForAnimationController delegate

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

Si vous google "tutoriel de transition personnalisé UINavigationController" et vous obtiendrez de nombreux hits. Ou voir vidéo WWDC 2013 Custom Transitions .

190
Rob

Vous voudrez peut-être ajouter le code suivant avant addSubview

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

D'une autre question custom-transition-for-Push-animation-with-navigationcontroller-on-ios-9

Extrait de la documentation Apple pour finalFrameForViewController:

Renvoie le rectangle du cadre de fin pour la vue du contrôleur de vue spécifié.

Le rectangle renvoyé par cette méthode représente la taille de la vue correspondante à la fin de la transition. Pour la vue couverte lors de la présentation, la valeur renvoyée par cette méthode peut être CGRectZero, mais il peut également s'agir d'un rectangle de cadre valide.

14
Q i

En utilisant les réponses parfaites de Rob's & Qi, voici le code simplifié Swift, utilisant la même animation de fondu pour .Push et .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()
    }
}

N'oubliez pas de définir le délégué dans la méthode viewDidLoad () de YourViewController:

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

Cela fonctionne à la fois Swift 3 et 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