Comment modifier les animations Push et Pop dans une application basée sur la navigation

J'ai une application basée sur la navigation et je veux changer l'animation des animations push et pop. Comment ferais-je cela?

    J'ai fait ce qui suit et ça marche bien … et c'est simple et facile à comprendre ..

    CATransition* transition = [CATransition animation]; transition.duration = 0.5; transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade //transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom [self.navigationController.view.layer addAnimation:transition forKey:nil]; [[self navigationController] popViewControllerAnimated:NO]; 

    Et la même chose pour pousser ..


    Version 3.0 de Swift:

     let transition = CATransition() transition.duration = 0.5 transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) transition.type = kCATransitionFade self.navigationController?.view.layer.add(transition, forKey: nil) _ = self.navigationController?.popToRootViewController(animated: false) 

    C'est comme ça que j'ai toujours réussi à accomplir cette tâche.

    Pour Push:

     MainView *nextView=[[MainView alloc] init]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDuration:0.75]; [self.navigationController pushViewController:nextView animated:NO]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO]; [UIView commitAnimations]; [nextView release]; 

    Pour Pop:

     [UIView beginAnimations:nil context:NULL]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDuration:0.75]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO]; [UIView commitAnimations]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDelay:0.375]; [self.navigationController popViewControllerAnimated:NO]; [UIView commitAnimations]; 

    Je reçois encore beaucoup de commentaires à ce sujet, donc je vais aller de l'avant et mettre à jour pour utiliser des blocs d'animation qui est la façon recommandée par Apple de faire des animations de toute façon.

    Pour Push:

     MainView *nextView = [[MainView alloc] init]; [UIView animateWithDuration:0.75 animations:^{ [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [self.navigationController pushViewController:nextView animated:NO]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO]; }]; 

    Pour Pop:

     [UIView animateWithDuration:0.75 animations:^{ [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO]; }]; [self.navigationController popViewControllerAnimated:NO]; 

    pour pousser

     CATransition *transition = [CATransition animation]; transition.duration = 0.3; transition.type = kCATransitionFade; //transition.subtype = kCATransitionFromTop; [self.navigationController.view.layer addAnimation:transition forKey:kCATransition]; [self.navigationController pushViewController:ViewControllerYouWantToPush animated:NO]; 

    pour la pop

     CATransition *transition = [CATransition animation]; transition.duration = 0.3; transition.type = kCATransitionFade; //transition.subtype = kCATransitionFromTop; [self.navigationController.view.layer addAnimation:transition forKey:kCATransition]; [self.navigationController popViewControllerAnimated:NO]; 

    Réponse @Magnus, seulement pour Swift (2.0)

      let transition = CATransition() transition.duration = 0.5 transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) transition.type = kCATransitionPush transition.subtype = kCATransitionFromTop self.navigationController!.view.layer.addAnimation(transition, forKey: nil) let writeView : WriteViewController = self.storyboard?.instantiateViewControllerWithIdentifier("WriteView") as! WriteViewController self.navigationController?.pushViewController(writeView, animated: false) 

    Quelques sidenotes:

    Vous pouvez le faire aussi avec Segue, il suffit de l'implémenter dans prepareForSegue ou shouldPerformSegueWithIdentifier . Cependant , cela conservera également l'animation par défaut. Pour résoudre ce problème, vous devez aller sur le story-board, cliquer sur la Segue, et décocher la case 'Animer'. Mais cela limitera votre application pour iOS 9.0 et supérieur (au less quand je l'ai fait dans Xcode 7).

    Dans une section, les deux dernières lignes doivent être remplacées par:

     self.navigationController?.popViewControllerAnimated(false) 

    Même si je mets faux, ça l'ignore en quelque sorte.

    L'utilisation d'appels privés est une mauvaise idée car Apple n'approuve plus les applications qui le font. Peut-être pourriez-vous essayer ceci:

     //Init Animation [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration: 0.50]; [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:YES]; //Create ViewController MyViewController *myVC = [[MyViewController alloc] initWith...]; [self.navigationController pushViewController:myVC animated:NO]; [myVC release]; //Start Animation [UIView commitAnimations]; 

    Rappelez-vous que dans Swift , l' extension est définitivement vos amis!

     public extension UINavigationController { /** Pop current view controller to previous view controller. - parameter type: transition animation type. - parameter duration: transition animation duration. */ func pop(transitionType type: Ssortingng = kCATransitionFade, duration: CFTimeInterval = 0.3) { self.addTransition(transitionType: type, duration: duration) self.popViewControllerAnimated(false) } /** Push a new view controller on the view controllers's stack. - parameter vc: view controller to push. - parameter type: transition animation type. - parameter duration: transition animation duration. */ func push(viewController vc: UIViewController, transitionType type: Ssortingng = kCATransitionFade, duration: CFTimeInterval = 0.3) { self.addTransition(transitionType: type, duration: duration) self.pushViewController(vc, animated: false) } private func addTransition(transitionType type: Ssortingng = kCATransitionFade, duration: CFTimeInterval = 0.3) { let transition = CATransition() transition.duration = duration transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) transition.type = type self.view.layer.addAnimation(transition, forKey: nil) } } 

    Il y a UINavigationControllerDelegate et UIViewControllerAnimatedTransitioning là vous pouvez changer l'animation pour tout ce que vous voulez.

    Par exemple, ceci est une animation pop verticale pour VC:

     @objc class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.5 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let containerView = transitionContext.containerView() let bounds = UIScreen.mainScreen().bounds containerView!.insertSubview(toViewController.view, belowSubview: fromViewController.view) toViewController.view.alpha = 0.5 let finalFrameForVC = fromViewController.view.frame UIView.animateWithDuration(transitionDuration(transitionContext), animations: { fromViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.height) toViewController.view.alpha = 1.0 }, completion: { finished in transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) }) } 

    }

    Et alors

     func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { if operation == .Pop { return PopAnimator() } return nil; } 

    Tutoriel utile https://www.objc.io/issues/5-ios7/view-controller-transitions/

    J'ai récemment essayé de faire quelque chose de similaire. J'ai décidé que je n'aimais pas l'animation coulissante de UINavigationController, mais je ne voulais pas non plus faire les animations que UIView vous donne comme curl ou quelque chose comme ça. Je voulais faire un fondu enchaîné entre les vues quand je pousse ou pop.

    Le problème réside dans le fait que la vue supprime littéralement la vue ou en fait apparaître une par-dessus la vue actuelle, de sorte qu'un fondu ne fonctionne pas. La solution que je suis venu impliquer en prenant ma nouvelle vue et en l'ajoutant comme une sous-vue à la vue de dessus actuelle sur la stack de UIViewController. Je l'ajoute avec un alpha de 0, puis fais un crossfade. Quand la séquence d'animation se termine, je pousse la vue sur la stack sans l'animer. Je returnne ensuite à l'ancien topView et nettoie les choses que j'avais changées.

    C'est un peu plus compliqué que ça, parce que vous avez les éléments de navigation que vous devez ajuster pour que la transition soit correcte. En outre, si vous effectuez une rotation, vous devez ajuster les tailles d'image lorsque vous ajoutez les vues en tant que sous-vues afin qu'elles s'affichent correctement à l'écran. Voici une partie du code que j'ai utilisé. J'ai sous-classé UINavigationController et replace les methods push et pop.

     -(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { UIViewController *currentViewController = [self.viewControllers lastObject]; //if we don't have a current controller, we just do a normal push if(currentViewController == nil) { [super pushViewController:viewController animated:animated]; return; } //if no animation was requested, we can skip the cross fade if(!animation) { [super pushViewController:viewController animated:NO]; return; } //start the cross fade. This is a sortingcky thing. We basically add the new view //as a subview of the current view, and do a cross fade through alpha values. //then we push the new view on the stack without animating it, so it seemlessly is there. //Finally we remove the new view that was added as a subview to the current view. viewController.view.alpha = 0.0; //we need to hold onto this value, we'll be releasing it later NSSsortingng *title = [currentViewController.title retain]; //add the view as a subview of the current view [currentViewController.view addSubview:viewController.view]; [currentViewController.view bringSubviewToFront:viewController.view]; UIBarButtonItem *rButtonItem = currentViewController.navigationItem.rightBarButtonItem; UIBarButtonItem *lButtonItem = currentViewController.navigationItem.leftBarButtonItem; NSArray *array = nil; //if we have a right bar button, we need to add it to the array, if not, we will crash when we try and assign it //so leave it out of the array we are creating to pass as the context. I always have a left bar button, so I'm not checking to see if it is nil. Its a little sloppy, but you may want to be checking for the left BarButtonItem as well. if(rButtonItem != nil) array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,rButtonItem,nil]; else { array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,nil]; } //remove the right bar button for our transition [currentViewController.navigationItem setRightBarButtonItem:nil animated:YES]; //remove the left bar button and create a backbarbutton looking item //[currentViewController.navigationItem setLeftBarButtonItem:nil animated:NO]; //set the back button UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:title style:kButtonStyle target:self action:@selector(goBack)]; [currentViewController.navigationItem setLeftBarButtonItem:backButton animated:YES]; [viewController.navigationItem setLeftBarButtonItem:backButton animated:NO]; [backButton release]; [currentViewController setTitle:viewController.title]; [UIView beginAnimations:@"push view" context:array]; [UIView setAnimationDidStopSelector:@selector(animationForCrossFadePushDidStop:finished:context:)]; [UIView setAnimationDelegate:self]; [UIView setAnimationDuration:0.80]; [viewController.view setAlpha: 1.0]; [UIView commitAnimations]; } -(void)animationForCrossFadePushDidStop:(NSSsortingng *)animationID finished:(NSNumber *)finished context:(void *)context { UIViewController *c = [(NSArray*)context objectAtIndex:0]; UIViewController *n = [(NSArray*)context objectAtIndex:1]; NSSsortingng *title = [(NSArray *)context objectAtIndex:2]; UIBarButtonItem *l = [(NSArray *)context objectAtIndex:3]; UIBarButtonItem *r = nil; //not all views have a right bar button, if we look for it and it isn't in the context, //we'll crash out and not complete the method, but the program won't crash. //So, we need to check if it is there and skip it if it isn't. if([(NSArray *)context count] == 5) r = [(NSArray *)context objectAtIndex:4]; //Take the new view away from being a subview of the current view so when we go back to it //it won't be there anymore. [[[c.view subviews] lastObject] removeFromSuperview]; [c setTitle:title]; [title release]; //set the search button [c.navigationItem setLeftBarButtonItem:l animated:NO]; //set the next button if(r != nil) [c.navigationItem setRightBarButtonItem:r animated:NO]; [super pushViewController:n animated:NO]; } 

    Comme je le mentionne dans le code, j'ai toujours un élément de button de barre gauche, donc je ne vérifie pas s'il est nul avant de le placer dans le tableau que je passe comme context pour le délégué d'animation. Si vous faites cela, vous voudrez peut-être faire cette vérification.

    Le problème que j'ai trouvé était que si vous plantez du tout dans la méthode déléguée, le programme ne tombera pas en panne. Cela empêche le délégué de terminer mais vous n'obtenez aucune sorte d'avertissement.
    Donc, puisque je faisais mon nettoyage dans cette routine de délégué, cela provoquait un comportement visuel bizarre, car il ne finissait pas le nettoyage.

    Le button de return que je crée appelle une méthode "goBack", et cette méthode appelle simplement la routine pop.

     -(void)goBack { [self popViewControllerAnimated:YES]; } 

    Aussi, voici ma routine pop.

     -(UIViewController *)popViewControllerAnimated:(BOOL)animated { //get the count for the number of viewControllers on the stack int viewCount = [[self viewControllers] count]; //get the top view controller on the stack UIViewController *topViewController = [self.viewControllers objectAtIndex:viewCount - 1]; //get the next viewController after the top one (this will be the new top one) UIViewController *newTopViewController = [self.viewControllers objectAtIndex:viewCount - 2]; //if no animation was requested, we can skip the cross fade if(!animated) { [super popViewControllerAnimated:NO]; return topViewController; } //start of the cross fade pop. A bit sortingcky. We need to add the new top controller //as a subview of the curent view controler with an alpha of 0. We then do a cross fade. //After that we pop the view controller off the stack without animating it. //Then the cleanup happens: if the view that was popped is not released, then we //need to remove the subview we added and change some titles back. newTopViewController.view.alpha = 0.0; [topViewController.view addSubview:newTopViewController.view]; [topViewController.view bringSubviewToFront:newTopViewController.view]; NSSsortingng *title = [topViewController.title retain]; UIBarButtonItem *lButtonItem = topViewController.navigationItem.leftBarButtonItem; UIBarButtonItem *rButtonItem = topViewController.navigationItem.rightBarButtonItem; //set the new buttons on top of the current controller from the new top controller if(newTopViewController.navigationItem.leftBarButtonItem != nil) { [topViewController.navigationItem setLeftBarButtonItem:newTopViewController.navigationItem.leftBarButtonItem animated:YES]; } if(newTopViewController.navigationItem.rightBarButtonItem != nil) { [topViewController.navigationItem setRightBarButtonItem:newTopViewController.navigationItem.rightBarButtonItem animated:YES]; } [topViewController setTitle:newTopViewController.title]; //[topViewController.navigationItem.leftBarButtonItem setTitle:newTopViewController.navigationItem.leftBarButtonItem.title]; NSArray *array = nil; if(rButtonItem != nil) array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,rButtonItem,nil]; else { array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,nil]; } [UIView beginAnimations:@"pop view" context:array]; [UIView setAnimationDidStopSelector:@selector(animationForCrossFadePopDidStop:finished:context:)]; [UIView setAnimationDelegate:self]; [UIView setAnimationDuration:0.80]; [newTopViewController.view setAlpha: 1.0]; [UIView commitAnimations]; return topViewController; } -(void)animationForCrossFadePopDidStop:(NSSsortingng *)animationID finished:(NSNumber *)finished context:(void *)context { UIViewController *c = [(NSArray *)context objectAtIndex:0]; //UIViewController *n = [(NSArray *)context objectAtIndex:1]; NSSsortingng *title = [(NSArray *)context objectAtIndex:1]; UIBarButtonItem *l = [(NSArray *)context objectAtIndex:2]; UIBarButtonItem *r = nil; //Not all views have a right bar button. If we look for one that isn't there // we'll crash out and not complete this method, but the program will continue. //So we need to check if it is therea nd skip it if it isn't. if([(NSArray *)context count] == 4) r = [(NSArray *)context objectAtIndex:3]; //pop the current view from the stack without animation [super popViewControllerAnimated:NO]; //if what was the current veiw controller is not nil, then lets correct the changes //we made to it. if(c != nil) { //remove the subview we added for the transition [[c.view.subviews lastObject] removeFromSuperview]; //reset the title we changed c.title = title; [title release]; //replace the left bar button that we changed [c.navigationItem setLeftBarButtonItem:l animated:NO]; //if we were passed a right bar button item, replace that one as well if(r != nil) [c.navigationItem setRightBarButtonItem:r animated:NO]; else { [c.navigationItem setRightBarButtonItem:nil animated:NO]; } } } 

    C'est à peu près tout. Vous aurez besoin de code supplémentaire si vous souhaitez implémenter des rotations. Vous devrez définir la taille d'image de vos vues que vous ajoutez en tant que sous-vues avant de les afficher, sinon vous rencontrerez des problèmes d'orientation, mais la dernière fois que vous avez vu l'affichage précédent, c'était portrait. Donc, vous l'ajoutez en tant que sous-vue et l'effacez, mais il apparaît comme un portrait, puis quand nous sautons sans animation, la même vue, mais celle qui est dans la stack, est maintenant le paysage. Le tout a l'air un peu génial. L'implémentation de la rotation de chacun est un peu différente, donc je n'ai pas inclus mon code ici.

    J'espère que ça aide certaines personnes. J'ai cherché partout quelque chose comme ça et je n'ai rien trouvé. Je ne pense pas que ce soit la réponse parfaite, mais cela fonctionne très bien pour moi à ce stade.

    Comme c'est le meilleur résultat sur Google, j'ai pensé que je partagerais ce que je pense être le plus sain d'esprit; qui consiste à utiliser l'API de transition iOS 7+. J'ai implémenté ceci pour iOS 10 avec Swift 3.

    Il est assez simple de combiner cela avec la façon dont UINavigationController entre deux controllers de vue si vous créez une sous-class de UINavigationController et renvoyez une instance d'une class conforme au protocole UIViewControllerAnimatedTransitioning .

    Par exemple, voici ma sous-class UINavigationController :

     class NavigationController: UINavigationController { init() { super.init(nibName: nil, bundle: nil) delegate = self } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension NavigationController: UINavigationControllerDelegate { public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { return NavigationControllerAnimation(operation: operation) } } 

    Vous pouvez voir que je lui ai assigné UINavigationControllerDelegate , et dans une extension de ma sous-class, UINavigationControllerDelegate la méthode dans UINavigationControllerDelegate qui vous permet de returnner un controller d'animation personnalisé (ie, NavigationControllerAnimation ). Ce controller d'animation personnalisé replacea l'animation de stock pour vous.

    Vous vous requestz probablement pourquoi je passe l'opération à l'instance NavigationControllerAnimation via son initialiseur. Je le fais de sorte que dans l'implémentation du protocole UIViewControllerAnimatedTransitioning , je sache quelle est l'opération (ie, 'push' ou 'pop'). Cela aide à savoir quel genre d'animation je devrais faire. La plupart du time, vous souhaitez effectuer une animation différente en fonction de l'opération.

    Le rest est assez standard. Implémentez les deux fonctions requirejses dans le protocole UIViewControllerAnimatedTransitioning et animez comme vous le souhaitez:

     class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning { let operation: UINavigationControllerOperation init(operation: UINavigationControllerOperation) { self.operation = operation super.init() } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.3 } public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return } let containerView = transitionContext.containerView if operation == .push { // do your animation for push } else if operation == .pop { // do your animation for pop } } } 

    Il est important de se callbacker que, pour chaque type d'opération (p. Ex. «Push» ou «pop»), les controllers de vue de et vers seront différents. Lorsque vous êtes dans une opération de poussée, le controller de vue sera celui qui est poussé. Lorsque vous êtes dans une opération pop, le controller d'affichage est celui qui est en cours de transition, et le controller d'affichage est celui qui est en train de se triggersr.

    En outre, le controller to vue doit être ajouté en tant que sous-vue de containerView dans le context de transition.

    Lorsque votre animation est terminée, vous devez appeler transitionContext.completeTransition(true) . Si vous faites une transition interactive, vous devrez renvoyer dynamicment un Bool à completeTransition(didComplete: Bool) , selon que la transition est terminée à la fin de l'animation.

    Enfin ( lecture facultative ), vous pourriez vouloir voir comment j'ai fait la transition sur laquelle je travaillais. Ce code est un peu plus hacky et je l'ai écrit assez rapidement donc je ne dirais pas que c'est du bon code d'animation mais ça montre quand même comment faire la partie animation.

    Le mien était une transition vraiment simple; Je voulais imiter la même animation que UINavigationController, mais à la place de l'animation 'next page over the top', je voulais mettre en place une animation 1: 1 de l'ancienne vue en même time que la nouvelle vue le controller apparaît. Cela a pour effet de faire apparaître les deux controllers de vue comme s'ils étaient épinglés l'un à l'autre.

    Pour l'opération push, cela nécessite d'abord de définir l'origine de la vue de toViewController sur l'écran x hors écran, en l'ajoutant comme sous-vue de containerView , en l'animant sur l'écran en mettant cette origin.x à zéro. En même time, fromViewController la vue de origin.x en mettant son origin.x hors de l'écran:

     toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0) containerView.addSubview(toViewController.view) UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [ UIViewAnimationOptions.curveEaseOut ], animations: { toViewController.view.frame = containerView.bounds fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0) }, completion: { (finished) in transitionContext.completeTransition(true) }) 

    L'opération pop est fondamentalement l'inverse. Ajoutez le toViewController tant que sous-vue du containerView , et animez le fromViewController vers la droite pendant que vous l'animez dans le toViewController depuis la gauche:

     containerView.addSubview(toViewController.view) UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [ UIViewAnimationOptions.curveEaseOut ], animations: { fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0) toViewController.view.frame = containerView.bounds }, completion: { (finished) in transitionContext.completeTransition(true) }) 

    Voici un aperçu de tout le file rapide:

    https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be

    Voici comment j'ai fait la même chose dans Swift:

    Pour Push:

      UIView.animateWithDuration(0.75, animations: { () -> Void in UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut) self.navigationController!.pushViewController(nextView, animated: false) UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false) }) 

    Pour Pop:

    Je l'ai fait un peu différemment à certaines des réponses ci-dessus – mais comme je suis nouveau dans le développement de Swift, ce n'est peut-être pas correct. J'ai remplacé viewWillDisappear:animated: et ajouté le code pop là-dedans:

      UIView.animateWithDuration(0.75, animations: { () -> Void in UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut) UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false) }) super.viewWillDisappear(animated) 

    En utilisant la réponse d'iJordan comme source d'inspiration, pourquoi ne pas simplement créer une catégorie sur UINavigationController à utiliser dans toute votre application au lieu de copyr / coller ce code d'animation partout?

    UINavigationController + Animation.h

     @interface UINavigationController (Animation) - (void) pushViewControllerWithFlip:(UIViewController*) controller; - (void) popViewControllerWithFlip; @end 

    UINavigationController + Animation.m

     @implementation UINavigationController (Animation) - (void) pushViewControllerWithFlip:(UIViewController *) controller { [UIView animateWithDuration:0.50 animations:^{ [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [self pushViewController:controller animated:NO]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO]; }]; } - (void) popViewControllerWithFlip { [UIView animateWithDuration:0.5 animations:^{ [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO]; }]; [self popViewControllerAnimated:NO]; } @end 

    Il suffit ensuite d'importer le file UINavigationController + Animation.h et de l'appeler normalement:

     [self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]]; [self.navigationController popViewControllerWithFlip]; 

    Alors que toutes les réponses sont excellentes et que la plupart fonctionnent très bien, il existe une méthode un peu plus simple qui permet d'get le même effet …

    Pour Push:

      NextViewController *nextViewController = [[NextViewController alloc] init]; // Shift the view to take the status bar into account CGRect frame = nextViewController.view.frame; frame.origin.y -= 20; frame.size.height += 20; nextViewController.view.frame = frame; [UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) { [self.navigationController pushViewController:nextViewController animated:NO]; }]; 

    Pour Pop:

      int numViewControllers = self.navigationController.viewControllers.count; UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view]; [UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) { [self.navigationController popViewControllerAnimated:NO]; }];} 

    I am not aware of any way you can change the transition animation publicly.

    If the "back" button is not necessary you should use modal view controllers to have the "push from bottom" / "flip" / "fade" / (≥3.2)"page curl" transitions.


    On the private side, the method -pushViewController:animated: calls the undocumented method -pushViewController:transition:forceImmediate: , so eg if you want a flip-from-left-to-right transition, you can use

     [navCtrler pushViewController:ctrler transition:10 forceImmediate:NO]; 

    You can't change the "pop" transition this way, however.

    See my answer to this question for a way to do it in far fewer lines of code. This method allows you to animate a pseudo-"Push" of a new view controller any way you like, and when the animation is done it sets up the Navigation Controller just as if you had used the standard Push method. My example lets you animate either a slide-in from the left or from the right. Code repeated here for convenience:

     -(void) showVC:(UIViewController *) nextVC rightToLeft:(BOOL) rightToLeft { [self addChildViewController:neighbor]; CGRect offscreenFrame = self.view.frame; if(rightToLeft) { offscreenFrame.origin.x = offscreenFrame.size.width * -1.0; } else if(direction == MyClimbDirectionRight) { offscreenFrame.origin.x = offscreenFrame.size.width; } [[neighbor view] setFrame:offscreenFrame]; [self.view addSubview:[neighbor view]]; [neighbor didMoveToParentViewController:self]; [UIView animateWithDuration:0.5 animations:^{ [[neighbor view] setFrame:self.view.frame]; } completion:^(BOOL finished){ [neighbor willMoveToParentViewController:nil]; [neighbor.view removeFromSuperview]; [neighbor removeFromParentViewController]; [[self navigationController] pushViewController:neighbor animated:NO]; NSMutableArray *newStack = [[[self navigationController] viewControllers] mutableCopy]; [newStack removeObjectAtIndex:1]; //self, just below top [[self navigationController] setViewControllers:newStack]; }]; } 

    Have a look at ADTransitionController , a drop in replacement for UINavigationController with custom transition animations (its API matches the API of UINavigationController) that we created at Applidium.

    You can use different pre-defined animations for push and pop actions such as Swipe , Fade , Cube , Carrousel , Zoom and so on.

    See my more detailed answer, using only public methods, here:

    Prevent the animation when clicking "Back" button in a navigation bar?

    …it's imperfect (it means re-implementing some of UINavigationController) – but it doesn't use any private methods, and it works.

    From the sample app, check out this variation. https://github.com/mpospese/MPFoldTransition/

     #pragma mark - UINavigationController(MPFoldTransition) @implementation UINavigationController(MPFoldTransition) //- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated - (void)pushViewController:(UIViewController *)viewController foldStyle:(MPFoldStyle)style { [MPFoldTransition transitionFromViewController:[self visibleViewController] toViewController:viewController duration:[MPFoldTransition defaultDuration] style:style completion:^(BOOL finished) { [self pushViewController:viewController animated:NO]; } ]; } - (UIViewController *)popViewControllerWithFoldStyle:(MPFoldStyle)style { UIViewController *toController = [[self viewControllers] objectAtIndex:[[self viewControllers] count] - 2]; [MPFoldTransition transitionFromViewController:[self visibleViewController] toViewController:toController duration:[MPFoldTransition defaultDuration] style:style completion:^(BOOL finished) { [self popViewControllerAnimated:NO]; } ]; return toController; } 

    Just use:

     ViewController *viewController = [[ViewController alloc] init]; UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; navController.navigationBarHidden = YES; [self presentViewController:navController animated:YES completion: nil]; [viewController release]; [navController release]; 

    Realising this is an old question. I still would like to post this answer, as I had some problems popping several viewControllers with the proposed answers. My solution is to subclass UINavigationController and override all the pop and push methods.

    FlippingNavigationController.h

     @interface FlippingNavigationController : UINavigationController @end 

    FlippingNavigationController.m:

     #import "FlippingNavigationController.h" #define FLIP_DURATION 0.5 @implementation FlippingNavigationController - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { [UIView transitionWithView:self.view duration:animated?FLIP_DURATION:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromRight animations:^{ [super pushViewController:viewController animated:NO]; } completion:nil]; } - (UIViewController *)popViewControllerAnimated:(BOOL)animated { return [[self popToViewController:[self.viewControllers[self.viewControllers.count - 2]] animated:animated] lastObject]; } - (NSArray *)popToRootViewControllerAnimated:(BOOL)animated { return [self popToViewController:[self.viewControllers firstObject] animated:animated]; } - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated { __block NSArray* viewControllers = nil; [UIView transitionWithView:self.view duration:animated?FLIP_DURATION:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromLeft animations:^{ viewControllers = [super popToViewController:viewController animated:NO]; } completion:nil]; return viewControllers; } @end 

    I found a mildly recursive way to do this that works for my purposes. I have an instance variable BOOL that I use to block the normal popping animation and substitute my own non-animated pop message. The variable is initially set to NO. When the back button is tapped, the delegate method sets it to YES and sends a new non-animated pop message to the nav bar, thereby calling the same delegate method again, this time with the variable set to YES. With the variable is set to YES, the delegate method sets it to NO and returns YES to allow the non-animated pop occur. After the second delegate call returns, we end up back in the first one, where NO is returned, blocking the original animated pop! It's actually not as messy as it sounds. My shouldPopItem method looks like this:

     - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item { if ([[navigationBar items] indexOfObject:item] == 1) { [expandedStack restack]; } if (!progPop) { progPop = YES; [navBar popNavigationItemAnimated:NO]; return NO; } else { progPop = NO; return YES; } } 

    Works for me.

    You can now use UIView.transition . Note that animated:false . This works with any transition option, pop, push, or stack replace.

     if let nav = self.navigationController { UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: { _ = nav.popViewController(animated:false) }, completion:nil) }