238 votes

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 dois-je faire ?

Edition 2018

Il y a eu de nombreuses réponses à cette question et cela fait un certain temps maintenant, j'ai re-choisi la réponse à ce que je crois être la plus pertinente maintenant. Si quelqu'un pense le contraire, faites-le moi savoir dans les commentaires.

26 votes

À partir d'iOS 7, il existe une API officielle pour cela ; voir UINavigationControllerDelegate La prise en charge des animations de transition personnalisées de l'entreprise. Il y a également un Vidéo de la WWDC 2013 à ce sujet.

0 votes

J'ai ajouté une réponse (ci-dessous) pour faire cela en Swift. Je suis tombé sur cette question concernant les implémentations Swift et j'ai pensé que je devais y participer avec mon implémentation ultérieure.

1 votes

Pour un très bon tutoriel avec l'API officielle (iOS 7+), voir : bradbambara.wordpress.com/2014/04/11/

274voto

Magnus Points 1081

J'ai fait ce qui suit et cela fonctionne 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 la poussée..


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)

37 votes

+1, c'est vraiment la solution la plus saine. Juste une petite remarque pour les futurs visiteurs : le Animated:NO est vital. Si YES est passée, les animations se mélangent et provoquent des effets bizarres.

12 votes

La meilleure solution jusqu'à présent Et pour les débutants, n'oubliez pas d'inclure QuartCore (#import <QuartzCore/QuartzCore.h>)

0 votes

Il semble que cette approche ait de meilleures performances, cependant comment faire tourner l'écran de gauche à droite comme la solution d'animation UIView avec l'option UIViewAnimationTransitionFlipFromLeft. J'ai essayé avec tous les types de transition mais je ne vois pas cet effet.

257voto

jordanperry Points 2298

C'est ainsi 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 toujours beaucoup de commentaires à ce sujet, alors je vais aller de l'avant et le mettre à jour pour utiliser des blocs d'animation, ce 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];

3 votes

Merci pour cela. Mais le pop est effectué automatiquement par le UINavigationController. Comment remplacer ce comportement pour pouvoir appeler votre logique d'ouverture personnalisée ?

1 votes

@stuckj en fait ça marche !!! vous devez juste remplacer super par self.navigationController

0 votes

Existe-t-il un moyen d'obtenir une diapositive de gauche au lieu de la diapositive de droite par défaut ?

29voto

Jennifer Points 159

Pour la poussée

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

11voto

nicktmro Points 1570

L'utilisation d'appels privés est une mauvaise idée car Apple n'approuve plus les applications qui font cela. Vous pouvez peut-être 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];

0 votes

Cela ne fonctionne qu'à moitié - cela ne résoudra pas le problème plus difficile des animations pop.

0 votes

J'aime mieux cette solution, et oui, elle fonctionne. L'utilisation de méthodes privées vous fera rejeter à coup sûr.

0 votes

@nicktmro qui est l'appel d'api privé. Je n'en ai pas remarqué.

4voto

georryan Points 123

J'ai récemment essayé 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 curl ou quelque chose comme ça. Je voulais faire un fondu enchaîné entre les vues lorsque je pousse ou que je saute.

Le problème réside dans le fait que la vue est littéralement en train de supprimer la vue ou d'en ajouter une par-dessus la vue actuelle, de sorte qu'un fondu ne fonctionne pas. La solution que j'ai trouvée consiste à prendre ma nouvelle vue et à l'ajouter en tant que sous-vue à la vue supérieure actuelle sur la pile du UIViewController. Je l'ajoute avec un alpha de 0, puis je fais un fondu enchaîné. Lorsque la séquence d'animation se termine, je pousse la vue sur la pile sans l'animer. Je retourne ensuite à l'ancienne vue supérieure et nettoie les éléments que j'ai modifiés.

C'est un peu plus compliqué que cela, car il faut ajuster les éléments de navigation pour que la transition soit correcte. De plus, si vous effectuez une rotation, vous devez ensuite ajuster la taille des cadres 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é le UINavigationController et surchargé 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 bouton de la barre de gauche, donc je ne vérifie pas s'il est nul avant de le mettre dans le tableau que je passe comme 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 rencontré est que si vous vous plantez dans la méthode du délégué, le programme ne se plante pas. Il empêche simplement le délégué de se terminer, mais vous n'obtenez aucune sorte d'avertissement.
Donc, comme je faisais mon nettoyage dans cette routine déléguée, cela provoquait un comportement visuel bizarre car le nettoyage n'était pas terminé.

Le bouton de retour 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 voulez 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 : l'orientation est paysage, mais la dernière fois que vous avez vu la vue précédente, elle était portrait. Donc, vous l'ajoutez en tant que sous-vue et vous l'introduisez en fondu mais elle apparaît en portrait, puis lorsque nous l'ouvrons sans animation, la même vue, mais celle qui est dans la pile, est maintenant en paysage. Tout cela a l'air un peu bizarre. L'implémentation de la rotation est un peu différente d'une personne à l'autre, donc je n'ai pas inclus mon code pour cela ici.

J'espère que ça aidera 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.

0 votes

Bien qu'admirable, ce n'est honnêtement pas la solution, 7 ans plus tard !

0 votes

Vous avez raison. Cette réponse date de 2011. Elle fonctionnait à l'époque, mais les choses ont beaucoup changé depuis. =)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X