web-dev-qa-db-fra.com

Comment modifier les animations Push et Pop dans une application de navigation

J'ai une application basée sur la navigation et je veux changer l'animation des animations Push et Pop. Comment je ferais ça?

Edit 2018

Il y a eu beaucoup de réponses à cette question et cela fait assez longtemps maintenant, j'ai choisi de nouveau la réponse à ce que je crois être le plus pertinent maintenant. S'il y a quelqu'un qui pense le contraire s'il vous plaît laissez-moi savoir dans les commentaires

204
Jab

Comment modifier les animations Push et Pop dans une application de navigation ...

Pour 2018, err 2019, la "réponse finale!"

PréambuleDisons que vous êtes novice dans le développement iOS, peut-être depuis Android. Confusément, Apple fournit deux (et seulement deux) transitions faciles à utiliser. Ce sont: "Crossfade" et "Flip". Maintenant, les deux transitions les plus inutiles sont "crossfade" et "flip"! Personne ne sait pourquoi Apple a fourni ces deux transitions inutiles en tant que transitions intégrées! Donc, étonnamment, si vous voulez faire les transitions les plus ordinaires}, comme "glisser", vous devez faire une énorme quantité de travail. Ce travail, est expliqué dans ce post.

  • Remarque 1: pour utiliser l'un des deux produits fournis par Apple (fondu enchaîné, flips), regardez la réponse de @PeterDeWeese sur cette page.

  • Note 2: Il y a des années, il y avait une quasi-transition rapide de CATransition. Ça ne marche pas.

Donc, pour répéter:

Étonnamment: avec iOS, si vous voulez juste la transition la plus simple, telle qu'une transition coulissante/coulissante ordinaire, vous devez implémenter une transition personnalisée complète.

Voici comment le faire ...

1. Vous avez besoin d'une UIViewControllerAnimatedTransitioning personnalisée

  1. Vous avez besoin de votre propre bool comme popStyle. (Est-ce que ça saute ou ça saute?)

  2. Vous devez inclure transitionDuration (trivial) et l'appel principal, animateTransition

  3. En fait, vous must écrivez deux routines différentes pour l'intérieur animateTransition. Un pour le Push et un pour le pop. Probablement les nommer animatePush et animatePop. Dans animateTransition, il suffit de créer une branche sur popStyle aux deux routines

  4. L'exemple ci-dessous fait un simple déménagement/déménagement

  5. Dans vos routines animatePush et animatePop. Vous doit obtenez le "de vue" et le "à voir". (Comment faire cela, est montré dans l'exemple de code.)

  6. et vous doitaddSubview pour la nouvelle vue "en".

  7. et vous devez appelez completeTransition à la fin de votre anime

Alors ..

  class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning {

        var popStyle: Bool = false

        func transitionDuration(
            using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.20
        }

        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

            if popStyle {

                animatePop(using: transitionContext)
                return
            }

            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!

            let f = transitionContext.finalFrame(for: tz)

            let fOff = f.offsetBy(dx: f.width, dy: 55)
            tz.view.frame = fOff

            transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)

            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    tz.view.frame = f
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }

        func animatePop(using transitionContext: UIViewControllerContextTransitioning) {

            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!

            let f = transitionContext.initialFrame(for: fz)
            let fOffPop = f.offsetBy(dx: f.width, dy: 55)

            transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)

            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    fz.view.frame = fOffPop
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }
    }

Et alors ...

2. Utilisez-le dans votre contrôleur de vue.

Remarque: étrangement, vous devez seulement faire ceci dans le "premier" contrôleur de vue. (Celui qui est "dessous".)

Avec celui que vous avez placé sur haut, faites rien. Facile.

Donc, votre classe ...

class SomeScreen: UIViewController {
}

devient...

class FrontScreen: UIViewController,
        UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {

    let simpleOver = SimpleOver()


    override func viewDidLoad() {

        super.viewDidLoad()
        navigationController?.delegate = self
    }

    func navigationController(
        _ navigationController: UINavigationController,
        animationControllerFor operation: UINavigationControllerOperation,
        from fromVC: UIViewController,
        to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

        simpleOver.popStyle = (operation == .pop)
        return simpleOver
    }
}

C'est tout.

Push et pop exactement comme d'habitude, pas de changement. Pousser ...

let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
          .instantiateViewController(withIdentifier: "nextScreenStoryboardID")
          as! NextScreen
navigationController?.pushViewController(n, animated: true)

et pour le faire apparaître, vous pouvez si vous le souhaitez simplement le faire sur l’écran suivant:

class NextScreen: TotallyOrdinaryUIViewController {

    @IBAction func userClickedBackOrDismissOrSomethingLikeThat() {

        navigationController?.popViewController(animated: true)
    }
}

"C'est si simple."

18
Fattie

J'ai fait ce qui suit et cela fonctionne bien .. et 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 Push ..


Version Swift 3.0:

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)
258
Magnus

C'est comme ça que j'ai toujours réussi à terminer 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 toujours beaucoup de retours de cette expérience, je vais donc le mettre à jour pour utiliser des blocs d’animation, ce qui est le moyen recommandé 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];
256
jordanperry

pour Push

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 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];
29
Ted

@Magnus répond, alors 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 notes:

Vous pouvez faire cela aussi avec Segue, implémentez simplement ceci dans prepareForSegue ou shouldPerformSegueWithIdentifier. Cependant , cela conservera également l'animation par défaut. Pour résoudre ce problème, vous devez aller sur le storyboard, cliquer sur le Segue et décocher la case 'Animates'. Mais cela limitera votre application pour IOS 9.0 et les versions ultérieures (du moins lorsque je l’ai fait dans Xcode 7).

En faisant la transition, les deux dernières lignes doivent être remplacées par:

self.navigationController?.popViewControllerAnimated(false)

Même si j'ai faux, cela l'ignore un peu.

18
CularBytes

N'oubliez pas que dans Swift , extension sont 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: String = 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: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: String = 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)
    }

}
15
Luca Davanzo

Utiliser des 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];
10
nicktmro

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

Il est très simple de combiner cela avec la façon dont UINavigationController s'anime entre deux contrôleurs de vue si vous créez une sous-classe de UINavigationController et renvoyez une instance d'une classe conforme au protocole UIViewControllerAnimatedTransitioning.

Par exemple, voici ma sous-classe 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 règle UINavigationControllerDelegate sur lui-même et que, dans une extension de ma sous-classe, j'implémente la méthode dans UINavigationControllerDelegate qui vous permet de renvoyer un contrôleur d'animation personnalisé (c'est-à-dire, NavigationControllerAnimation). Ce contrôleur d'animation personnalisé remplacera l'animation standard pour vous.

Vous vous demandez probablement pourquoi je passe l'opération à l'instance NavigationControllerAnimation via son initialiseur. Je le fais de sorte que, dans la mise en œuvre du protocole NavigationControllerAnimation dans UIViewControllerAnimatedTransitioning, je connaisse l’opération (c’est-à-dire «push» ou «pop»). Cela aide à savoir quel type d'animation je devrais faire. La plupart du temps, vous souhaitez effectuer une animation différente en fonction de l'opération.

Le reste est assez standard. Implémentez les deux fonctions requises dans le protocole UIViewControllerAnimatedTransitioning et animez comme bon vous semble:

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 rappeler que pour chaque type d'opération (c'est-à-dire «Push» ou «Pop»), les contrôleurs de vue vers et depuis seront différents. Lorsque vous êtes en mode Push, le contrôleur à afficher est celui qui est poussé. Lorsque vous effectuez une opération contextuelle, le contrôleur to view sera celui qui est transféré et le contrôleur from view sera celui qui sera affiché.

De plus, le contrôleur de vue to doit être ajouté en tant que sous-vue de la containerView dans le contexte de transition.

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

Enfin (lecture facultative), vous voudrez peut-être voir comment j'ai effectué la transition sur laquelle je travaillais. Ce code est un peu plus compliqué et je l'ai écrit assez rapidement, je ne dirais donc pas que c'est un excellent code d'animation, mais il montre quand même comment faire la partie animation.

La mienne était une transition très simple. Je voulais imiter la même animation que UINavigationController le fait généralement, mais au lieu de l'animation "page suivante au-dessus", je souhaitais implémenter une animation 1: 1 de l'ancien contrôleur de vue en même temps que la nouvelle vue. contrôleur apparaît. Cela a pour effet de donner l'impression que les deux contrôleurs de vue sont épinglés l'un à l'autre.

Pour l'opération Push, il faut d'abord définir l'origine de la vue toViewController sur l'écran hors axe x, l'ajouter en tant que sous-vue de la containerView et l'animer à l'écran en définissant ce Origin.x à zéro. En même temps, j'anime la vue de la fromViewController en réglant son Origin.x à 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 la toViewController en tant que sous-vue de la containerView et déplacez la fromViewController à droite tout en animant la toViewController à 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 résumé avec l'intégralité du fichier Swift:

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

8
Alan Zeino

Il y a UINavigationControllerDelegate et UIViewControllerAnimatedTransitioning. Vous pouvez modifier l'animation comme bon vous semble.

Par exemple, il s'agit d'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/

6
eilas

Basé sur jordanperryanswer mis à jour pour Swift 4

Pour Push UIViewController

let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController
    UIView.animate(withDuration: 0.75, animations: {() -> Void in
    UIView.setAnimationCurve(.easeInOut)
    self.navigationController?.pushViewController(terms, animated: true)
    UIView.setAnimationTransition(.flipFromRight, for: (self.navigationController?.view)!, cache: false)
})

Pour Pop

UIView.animate(withDuration: 0.75, animations: {() -> Void in
    UIView.setAnimationCurve(.easeInOut)
    UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false)
})
navigationController?.popViewController(animated: false)
5
vp2698

Voici comment j'ai fait la même chose à 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:

En fait, je l'ai fait un peu différemment par rapport à certaines des réponses ci-dessus - mais comme je suis novice dans le développement de Swift, il se peut que ce ne soit pas correct. J'ai remplacé viewWillDisappear:animated: et ajouté le code pop à cet endroit:

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

    super.viewWillDisappear(animated)
5
djbp

J'essayais récemment de faire quelque chose de similaire. J'ai décidé que je n'aimais pas l'animation de glissement de UINavigationController, mais je ne voulais pas non plus faire les animations que UIView vous donne comme des boucles ou quelque chose comme ça. Je voulais faire un fondu enchaîné entre les vues lorsque je Push ou Pop. 

Le problème réside dans le fait que la vue la supprime ou la recouvre littéralement par-dessus la vue actuelle, de sorte qu'un fondu ne fonctionne pas. La solution à laquelle je suis arrivé consistait à prendre ma nouvelle vue et à l'ajouter en tant que sous-vue à la vue de dessus actuelle sur la pile d'UIViewController. Je l'ajoute avec un alpha de 0, puis fais un fondu enchaîné. Lorsque la séquence d'animation est terminée, I Poussez la vue dans la pile sans l'animer. Je retourne ensuite à l'ancien topView et nettoie les éléments que j'avais modifiés.

C'est un peu plus compliqué que cela, car vous avez les éléments de navigation que vous devez ajuster pour que la transition soit correcte. De plus, si vous effectuez une rotation, vous devez ensuite ajuster la taille des images au fur et à mesure que vous ajoutez les vues sous forme de vues secondaires afin qu'elles s'affichent correctement à l'écran. Voici une partie du code que j'ai utilisé. J'ai sous-classé UINavigationController et remplacé les méthodes 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 tricky 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
    NSString *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:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{

UIViewController *c = [(NSArray*)context objectAtIndex:0];
UIViewController *n = [(NSArray*)context objectAtIndex:1];
NSString *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 du bouton de barre de gauche; je ne vérifie donc pas s'il est égal à zéro avant de le placer dans le tableau que je passe en tant que contexte 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é est que si vous plantez du tout dans la méthode delegate, le programme ne se plantera pas. Cela empêche simplement le délégué de terminer mais vous ne recevez aucun type d'avertissement.
.__ Donc, comme je faisais mon nettoyage dans cette routine de délégué, cela provoquait un comportement visuel étrange, car il ne terminait pas le nettoyage.

Le bouton Précédent 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 tricky.  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];
NSString *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:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
 {

UIViewController *c = [(NSArray *)context objectAtIndex:0];
//UIViewController *n = [(NSArray *)context objectAtIndex:1];
NSString *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. Avant de les afficher, vous devez définir la taille du cadre des vues que vous ajoutez en tant que sous-vues. Sinon, vous rencontrerez des problèmes d'orientation, mais la dernière fois que vous avez vu la vue précédente, c'était un portrait. Donc, vous l'ajoutez en tant que sous-vue et vous le fondez, mais il apparaît sous forme de portrait, puis lorsque vous affichez sans animation, la même vue, mais celle qui se trouve dans la pile est maintenant paysage. Le tout a l'air un peu funky. La mise en œuvre de la rotation par tout le monde est un peu différente, donc je n'ai pas inclus mon code pour cela 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 vraiment bien pour moi à ce stade.

4
georryan

En vous inspirant de la réponse de iJordan, pourquoi ne pas simplement créer une catégorie sur UINavigationController à utiliser dans toute votre application au lieu de copier/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

Ensuite, importez simplement le fichier UINavigationController + Animation.h et appelez-le normalement:

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

[self.navigationController popViewControllerWithFlip];
3
DiscDev

Bien que toutes les réponses soient bonnes et que la plupart fonctionnent très bien, il existe une méthode légèrement plus simple qui produit 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];
  }];}
2
head in the codes

Vous pouvez maintenant utiliser UIView.transition. Notez que animated:false. Cela fonctionne avec n'importe quelle option de transition, pop, push ou pile.

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

C'est très simple

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
2
user2501116

Consultez ADTransitionController , un remplacement immédiat de UINavigationController avec des animations de transition personnalisées (son API correspond à l’API de UINavigationController) que nous avons créée à Applidium. 

Vous pouvez utiliser différentes animations prédéfinies pour les actions Push et pop telles que glisser, fondu, cube, Carrousel, Zoom et ainsi de suite.

2
felginep

Voir ma réponse à cette question pour un moyen de le faire en beaucoup moins de lignes de code. Cette méthode vous permet d’animer à votre guise le pseudo "Push" d’un nouveau contrôleur de vue. Lorsque l’animation est terminée, elle configure le contrôleur de navigation comme si vous utilisiez la méthode Push standard. Mon exemple vous permet d’animer un diaporama de gauche à droite . Code répété ici pour plus de commodité:

-(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];
    }];
}
1
RobP

Je ne suis au courant d'aucun moyen de modifier publiquement l'animation de la transition. 

Si le bouton "Précédent" n'est pas nécessaire, vous devez utiliser les contrôleurs de vue modaux pour que la page "Push from bottom"/"bascule"/"fondu"/(≥3.2) " curl "transitions.


Du côté de private, la méthode -pushViewController:animated: appelle la méthode non documentée -pushViewController:transition:forceImmediate:, par exemple. si vous voulez une transition de gauche à droite, vous pouvez utiliser

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

Vous ne pouvez pas changer la transition "pop" de cette façon, cependant.

0
kennytm

Réponse de Luca Davanzo dans Swift 4.2

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: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.popViewController(animated: 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: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        let transition = CATransition()
        transition.duration = duration
        transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        transition.type = type
        self.view.layer.add(transition, forKey: nil)
    }

}
0
Serj Rubens

À partir de l’exemple d’application, découvrez cette variante . 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;
}
0
johndpope

Réaliser que c'est une vieille question. Je voudrais quand même poster cette réponse, car j’ai eu quelques problèmes avec plusieurs réponses viewControllers avec les réponses proposées. Ma solution est de sous-classe UINavigationController et de remplacer toutes les méthodes pop et push.

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
0
Christian Otkjær

Je sais que ce fil est vieux, mais je pensais mettre mes deux sous. Vous n'avez pas besoin de créer une animation personnalisée, il existe un moyen simple (peut-être hacky) de le faire. Au lieu d'utiliser Push, créez un nouveau contrôleur de navigation, définissez le nouveau contrôleur de vue comme contrôleur de vue racine de ce contrôleur de navigation, puis présentez le contrôleur de navigation à partir du contrôleur de navigation d'origine. Present est facilement personnalisable avec de nombreux styles et ne nécessite pas de créer une animation personnalisée.

Par exemple:

UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)

Et vous pouvez changer le style de présentation comme vous le souhaitez.

0
Ethan Zhao

Il suffit d'utiliser:

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];
0
Sergio Andreotti