141 votes

Animer le changement des contrôleurs de vue sans utiliser la pile de contrôleurs de navigation, les sous-vues ou les contrôleurs modaux ?

Les NavigationControllers ont des piles ViewController à gérer, et des transitions d'animation limitées.

L'ajout d'un contrôleur de vue en tant que sous-vue à un contrôleur de vue existant nécessite le passage d'événements au contrôleur de sous-vue, ce qui est pénible à gérer, est chargé de petits désagréments et, en général, ressemble à un mauvais hack lors de la mise en œuvre (Apple recommande également de ne pas faire cela).

La présentation d'un contrôleur d'affichage modal place à nouveau un contrôleur d'affichage au-dessus d'un autre et, bien qu'il n'y ait pas les problèmes de passage d'événements décrits ci-dessus, le contrôleur d'affichage n'est pas vraiment "échangé", il est empilé.

Les storyboards, limités à iOS 5, sont presque idéaux, mais ne peuvent pas être utilisés dans tous les projets.

Quelqu'un peut-il présenter un EXEMPLE DE CODE SOLIDE sur un moyen de changer les contrôleurs de vue sans les limitations ci-dessus et permettant des transitions animées entre eux ?

Un exemple proche, mais pas d'animation : Comment utiliser plusieurs contrôleurs de vue personnalisés iOS sans contrôleur de navigation ?

Edit : L'utilisation de Nav Controller est bien, mais il doit y avoir des styles de transition animés (pas seulement les effets de glissement) le contrôleur de vue qui est montré doit être complètement interverti (pas empilé). Si le deuxième contrôleur de vue doit retirer un autre contrôleur de vue de la pile, alors il n'est pas assez encapsulé.

Edit 2 : iOS 4 devrait être l'OS de base pour cette question, j'aurais dû le préciser en mentionnant les storyboards (ci-dessus).

1 votes

Vous pouvez réaliser des transitions d'animation personnalisées avec un contrôleur de navigation. Si cela vous convient, veuillez supprimer cette contrainte de votre question et je publierai un exemple de code.

0 votes

@Richard si cela évite les tracas de la gestion de la pile et permet différents styles de transition animée entre les contrôleurs de vue, alors l'utilisation du contrôleur de navigation est parfaite !

0 votes

Ok bien. Je me suis impatienté et j'ai posté le code. Essayez-le. Ca marche pour moi.

107voto

XJones Points 14327

EDIT : Nouvelle réponse qui fonctionne dans n'importe quelle orientation. La réponse originale ne fonctionne que lorsque l'interface est en orientation portrait. En effet, les animations de transition de vue qui remplacent une vue par une autre doivent se produire avec des vues situées au moins un niveau en dessous de la première vue ajoutée à la fenêtre (par ex. window.rootViewController.view.anotherView ).

J'ai implémenté une classe conteneur simple que j'ai appelée TransitionController . Vous pouvez le trouver à l'adresse suivante https://gist.github.com/1394947 .

Par ailleurs, je préfère l'implémentation dans une classe séparée car elle est plus facile à réutiliser. Si vous ne voulez pas cela, vous pouvez simplement implémenter la même logique directement dans votre délégué d'application, éliminant ainsi la nécessité de la classe TransitionController classe. La logique dont vous auriez besoin serait cependant la même.

Utilisez-le comme suit :

Dans votre délégué d'application

// add a property for the TransitionController

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    MyViewController *vc = [[MyViewContoller alloc] init...];
    self.transitionController = [[TransitionController alloc] initWithViewController:vc];
    self.window.rootViewController = self.transitionController;
    [self.window makeKeyAndVisible];
    return YES;
}

Pour passer à un nouveau contrôleur de vue à partir de n'importe quel contrôleur de vue

- (IBAction)flipToView
{
    anotherViewController *vc = [[AnotherViewController alloc] init...];
    MyAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
    [appDelegate.transitionController transitionToViewController:vc withOptions:UIViewAnimationOptionTransitionFlipFromRight];
}

EDIT : Réponse originale ci-dessous - ne fonctionne que pour l'orientation du portrait.

J'ai fait les hypothèses suivantes pour cet exemple :

  1. Vous avez un contrôleur de vue assigné en tant que rootViewController de votre fenêtre

  2. Lorsque vous passez à une nouvelle vue, vous souhaitez remplacer le viewController actuel par le viewController appartenant à la nouvelle vue. À tout moment, seul le viewController actuel est vivant (c'est-à-dire alloué).

Le code peut être facilement modifié pour fonctionner différemment, le point clé est la transition animée et le contrôleur de vue unique. Assurez-vous que vous ne conservez pas de contrôleur de vue ailleurs que dans l'assignation à window.rootViewController .

Code pour animer la transition dans le délégué de l'application

- (void)transitionToViewController:(UIViewController *)viewController
                    withTransition:(UIViewAnimationOptions)transition
{
    [UIView transitionFromView:self.window.rootViewController.view
                        toView:viewController.view
                      duration:0.65f
                       options:transition
                    completion:^(BOOL finished){
                        self.window.rootViewController = viewController;
                    }];
}

Exemple d'utilisation dans un contrôleur de vue

- (IBAction)flipToNextView
{
    AnotherViewController *anotherVC = [[AnotherVC alloc] init...];
    MyAppDelegate *appDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
    [appDelegate transitionToViewController:anotherVC
                             withTransition:UIViewAnimationOptionTransitionFlipFromRight];
}

1 votes

Oui, très bien ! Non seulement il fait l'affaire, mais c'est un exemple de code très simple et propre. Merci beaucoup !

0 votes

Cela ne vous pose-t-il pas des problèmes dans le paysage ? Par ailleurs, cela déclenche-t-il les méthodes willAppear et didAppear des viewControllers ?

1 votes

Je sais que les appels d'apparence sont faits parce que je les ai enregistrés. Je ne vois pas non plus pourquoi cela affecterait les changements d'orientation. Pouvez-vous nous expliquer pourquoi ?

67voto

timthetoolman Points 3753

Vous pouvez utiliser le nouveau système de confinement viewController d'Apple. Pour plus d'informations, consultez la vidéo de la session WWDC 2011 "Implementing UIViewController Containment".

Nouveau dans iOS5, UIViewController Le confinement vous permet d'avoir un viewController parent et un certain nombre de viewControllers enfants qui sont contenus dans celui-ci. C'est ainsi que fonctionne le UISplitViewController. En procédant ainsi, vous pouvez empiler les contrôleurs de vue dans un parent, mais pour votre application particulière, vous utilisez simplement le parent pour gérer la transition d'un contrôleur de vue visible à un autre. C'est la façon de faire approuvée par Apple et l'animation d'un viewController enfant est sans problème. De plus, vous pouvez utiliser tous les différents UIViewAnimationOption transitions !

En outre, avec UIViewContainment, vous n'avez pas à vous soucier, sauf si vous le souhaitez, de la gestion désordonnée des viewControllers enfants pendant les événements d'orientation. Vous pouvez simplement utiliser ce qui suit pour vous assurer que votre parentViewController transmet les événements de rotation aux viewControllers enfants.

- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{
    return YES;
}

Vous pouvez faire ce qui suit ou quelque chose de similaire dans la méthode viewDidLoad de votre parent pour configurer le premier childViewController :

[self addChildViewController:self.currentViewController];
[self.view addSubview:self.currentViewController.view];
[self.currentViewController didMoveToParentViewController:self];
[self.currentViewController.swapViewControllerButton setTitle:@"Swap" forState:UIControlStateNormal];

ensuite, lorsque vous devez modifier le viewController enfant, vous appelez quelque chose du type de ce qui suit dans le viewController parent :

-(void)swapViewControllers:(childViewController *)addChildViewController:aNewViewController{
     [self addChildViewController:aNewViewController];
     __weak __block ViewController *weakSelf=self;
     [self transitionFromViewController:self.currentViewController
                       toViewController:aNewViewController
                               duration:1.0
                                options:UIViewAnimationOptionTransitionCurlUp
                             animations:nil
                             completion:^(BOOL finished) {
                                   [aNewViewController didMoveToParentViewController:weakSelf];

                                   [weakSelf.currentViewController willMoveToParentViewController:nil];
                                   [weakSelf.currentViewController removeFromParentViewController];

                                   weakSelf.currentViewController=[aNewViewController autorelease];
                             }];
 }

J'ai posté un projet d'exemple complet ici : https://github.com/toolmanGitHub/stackedViewControllers . Ce site autre projet montre comment utiliser UIViewController Container sur certains types de viewController d'entrée variés qui ne prennent pas tout l'écran. Bonne chance

1 votes

Un excellent exemple. Merci d'avoir pris le temps de poster le code. Par contre, il est limité à iOS 5 seulement. Comme mentionné dans la question : "[Les storyboards] sont limités à iOS 5, et sont presque idéaux, mais ne peuvent pas être utilisés dans tous les projets." Considérant qu'un grand pourcentage (environ 40% ?) de clients utilisent encore iOS 4, l'objectif est de fournir quelque chose qui fonctionne dans iOS 4 et plus.

0 votes

Vous ne devriez pas appeler [self.currentViewController willMoveToParentViewController:nil]; avant la transition ?

0 votes

@FelixLam - D'après la documentation sur le confinement de UIViewController, vous appelez willMoveToParentViewController uniquement si vous surchargez la méthode addChildViewController. Dans cet exemple, je l'appelle mais je ne la surcharge pas.

7voto

Richard Brightwell Points 2043

OK, je sais que la question dit de ne pas utiliser de contrôleur de navigation, mais il n'y a aucune raison de ne pas le faire. L'OP ne répondait pas aux commentaires à temps pour que j'aille me coucher. Ne me votez pas :)

Voici comment faire sauter le contrôleur de vue actuel et basculer vers un nouveau contrôleur de vue en utilisant un contrôleur de navigation :

UINavigationController *myNavigationController = self.navigationController;
[[self retain] autorelease];

[myNavigationController popViewControllerAnimated:NO];

PreferencesViewController *controller = [[PreferencesViewController alloc] initWithNibName:nil bundle:nil];

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.65];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:myNavigationController.view cache:YES];
[myNavigationController pushViewController:controller animated:NO];
[UIView commitAnimations];

[controller release];

0 votes

Cela n'empile-t-il pas les contrôleurs de vue ?

1 votes

Oui, car nous utilisons un contrôleur de navigation. Cependant, cela permet de contourner la limitation du type de transitions que vous pouvez effectuer, ce qui me semblait être le cœur de votre question.

0 votes

Fermer, mais l'un des gros problèmes est qu'il y a plusieurs contrôleurs de vue à gérer sur la pile. Je suis à la recherche d'un moyen de changer complètement les contrôleurs de vues. =)

3voto

Ryan Artecona Points 1750

Comme je viens de tomber sur ce problème exact, et que j'ai essayé des variantes de toutes les réponses préexistantes avec un succès limité, je vais poster comment j'ai fini par le résoudre :

Comme décrit dans ce billet sur les séquences personnalisées il est en fait très facile de faire des enchaînements personnalisés. Elles sont également très faciles à mettre en place dans Interface Builder, elles permettent de rendre visibles les relations dans IB et elles ne nécessitent pas une grande prise en charge par les contrôleurs de vue source/destination de la segue.

L'article dont le lien figure ci-dessus fournit un code iOS 4 permettant de remplacer le contrôleur de vue supérieur actuel de la pile navigationController par un nouveau contrôleur utilisant une animation de type "slide-in-from-top".

Dans mon cas, je voulais qu'une séquence de remplacement similaire se produise, mais avec une FlipFromLeft transition. J'avais également besoin d'un support pour iOS 5+ uniquement. Code :

De RAFlipReplaceSegue.h :

#import <UIKit/UIKit.h>

@interface RAFlipReplaceSegue : UIStoryboardSegue
@end

De RAFlipReplaceSegue.m :

#import "RAFlipReplaceSegue.h"

@implementation RAFlipReplaceSegue

-(void) perform
{
    UIViewController *destVC = self.destinationViewController;
    UIViewController *sourceVC = self.sourceViewController;
    [destVC viewWillAppear:YES];

    destVC.view.frame = sourceVC.view.frame;

    [UIView transitionFromView:sourceVC.view
                        toView:destVC.view
                      duration:0.7
                       options:UIViewAnimationOptionTransitionFlipFromLeft
                    completion:^(BOOL finished)
                    {
                        [destVC viewDidAppear:YES];

                        UINavigationController *nav = sourceVC.navigationController;
                        [nav popViewControllerAnimated:NO];
                        [nav pushViewController:destVC animated:NO];
                    }
     ];
}

@end

Maintenant, faites un control-drag pour mettre en place n'importe quel autre type de segue, puis faites-en une segue personnalisée, et tapez le nom de la classe de segue personnalisée, et voilà !

0 votes

Existe-t-il un moyen de le faire de manière programmatique sans storyboard ?

0 votes

Pour autant que je sache, pour utiliser une segue, il faut la définir et lui donner un identifiant dans un storyboard. Vous pouvez invoquer une segue dans le code avec la méthode UIViewController –performSegueWithIdentifier:sender: méthode.

1 votes

Vous ne devez jamais appeler viewDidXXX et viewWillXXX directement.

2voto

aopsfan Points 1694

J'ai lutté avec celui-ci pendant longtemps, et l'un de mes problèmes est répertorié aquí Je ne sais pas si vous avez eu ce problème. Mais voici ce que je vous recommande si cela doit fonctionner avec iOS 4.

Tout d'abord, créez un nouveau NavigationController classe. C'est ici que nous ferons tout le sale boulot - les autres classes pourront appeler "proprement" les méthodes d'instance comme pushViewController: et autres. Dans votre .h :

@interface NavigationController : UIViewController {
    NSMutableArray *childViewControllers;
    UIViewController *currentViewController;
}

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL))completion;
- (void)addChildViewController:(UIViewController *)childController;
- (void)removeChildViewController:(UIViewController *)childController;

Le tableau des contrôleurs de vue enfants servira de stockage pour tous les contrôleurs de vue de notre pile. Nous transmettrons automatiquement tout le code de rotation et de redimensionnement depuis le tableau des contrôleurs de vue enfants. NavigationController de la vue sur le currentController .

Maintenant, dans notre mise en œuvre :

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL))completion
{
    currentViewController = [toViewController retain];
    // Put any auto- and manual-resizing handling code here

    [UIView animateWithDuration:duration animations:animations completion:completion];

    [fromViewController.view removeFromSuperview];
}

- (void)addChildViewController:(UIViewController *)childController {
    [childViewControllers addObject:childController];
}

- (void)removeChildViewController:(UIViewController *)childController {
    [childViewControllers removeObject:childController];
}

Vous pouvez maintenant mettre en œuvre votre propre pushViewController: , popViewController et autres, en utilisant ces appels de méthode.

Bonne chance, et j'espère que cela vous aidera !

0 votes

Une fois encore, nous devons ajouter des contrôleurs de vue en tant que sous-vues de contrôleurs de vue existants. Certes, c'est ce que fait le contrôleur de navigation existant, mais cela signifie que nous devons essentiellement le réécrire, ainsi que toutes ses méthodes. En réalité, nous devrions éviter de devoir distribuer viewWillAppear et des méthodes similaires. Je commence à penser qu'il n'y a pas de moyen propre de le faire. Cependant, je vous remercie d'avoir pris le temps et de faire des efforts !

0 votes

Je pense qu'avec ce système d'ajout et de suppression de contrôleurs de vue selon les besoins, cette solution vous évite de devoir faire suivre ces méthodes.

0 votes

Vous êtes sûr ? J'avais l'habitude de permuter les contrôleurs de vue d'une manière similaire auparavant et je devais toujours faire suivre les messages. Pouvez-vous confirmer le contraire ?

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