web-dev-qa-db-fra.com

Animer des contrôleurs de changement de vue sans utiliser de pile de contrôleur de navigation, de sous-vues ou de 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 la transmission d'événements au contrôleur de sous-vue, ce qui est fastidieux à gérer, chargé avec peu d'ennuis et qui ressemble en général à un mauvais bidouillage lors de la mise en œuvre (Apple recommande également de ne pas Ce faisant).

Si vous présentez à nouveau un contrôleur de vue modal, vous placez un contrôleur de vue au-dessus d'un autre. Même s'il ne présente pas les problèmes de transmission d'événement décrits ci-dessus, il ne "remplace" pas vraiment le contrôleur de vue, il l'empile.

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

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

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

Edit: le contrôleur de navigation est utilisable, mais il doit exister des styles de transition animés (pas simplement des effets de diapositive), le contrôleur de vue affiché doit être complètement échangé (et non empilé). Si le deuxième contrôleur de vue doit supprimer un autre contrôleur de vue de la pile, il n'est pas suffisamment encapsulé.

Edit 2: iOS 4 devrait être le système d’exploitation de base de cette question, j’aurais dû préciser que lorsqu’il est question de storyboards (ci-dessus).

141
TigerCoding

EDIT: nouvelle réponse qui fonctionne dans n'importe quelle orientation. La réponse d'origine ne fonctionne que lorsque l'interface est en orientation portrait. Il s’agit d’animations de transition de vue b/c qui remplacent une vue et une vue différente doit se produire, les vues se trouvant au moins à un niveau inférieur à la première vue ajoutée à la fenêtre (par exemple, window.rootViewController.view.anotherView).

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

En passant, je préfère la mise en œuvre dans une classe séparée, car il est plus facile de la réutiliser. Si vous ne le souhaitez pas, vous pouvez simplement implémenter la même logique directement dans votre délégué d'application, éliminant ainsi le besoin de la classe TransitionController. La logique dont vous auriez besoin serait la même cependant.

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 portrait

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

  1. Vous avez un contrôleur de vue assigné comme le rootViewController de votre fenêtre

  2. Lorsque vous passez à une nouvelle vue, vous souhaitez remplacer le viewController actuel par le viewController propriétaire de la nouvelle vue. À tout moment, seul le viewController actuel est actif (par exemple, alloué).

Le code peut être facilement modifié pour fonctionner différemment, le point clé étant la transition animée et le contrôleur de vue unique. Assurez-vous de ne pas conserver un contrôleur de vue ailleurs que de l'attribuer à 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];
}
108
XJones

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 Containment vous permet d’avoir un viewController parent et un certain nombre de viewControllers enfants qu’il contient. Voici comment fonctionne UISplitViewController. Cela vous permet d'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 viewController visible à un autre. C’est la manière approuvée par Apple de faire les choses et d’animer à partir d’un enfant viewController est indolore. De plus, vous pouvez utiliser toutes les différentes transitions UIViewAnimationOption!

De plus, avec UIViewContainment, vous n'avez pas à vous soucier du désordre lié à la gestion du viewControllers enfant lors d'événements d'orientation. Vous pouvez simplement utiliser ce qui suit pour vous assurer que votre parentViewController transmet les événements de rotation aux enfants viewControllers.

- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{
    return YES;
}

Vous pouvez effectuer les opérations suivantes ou similaires 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];

puis, lorsque vous devez modifier le viewController enfant, vous appelez quelque chose comme 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 exemple de projet complet ici: https://github.com/toolmanGitHub/stackedViewControllers . Ce autre projet montre comment utiliser UIViewController Containment sur différents types de viewController d’entrée qui ne prennent pas tout l’écran. Bonne chance

67
timthetoolman

OK, je sais que la question dit sans utiliser de contrôleur de navigation, mais aucune raison de ne pas le faire. OP ne répondait pas aux commentaires à temps pour que je puisse dormir. Ne me reproche pas. :)

Voici comment afficher le contrôleur de vue actuel et basculer vers un nouveau contrôleur de vue à l'aide d'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];
7
Richard Brightwell

Puisque je viens de rencontrer ce problème et d'essayer différentes variantes de toutes les réponses préexistantes à un succès limité, je vais vous expliquer comment je l'ai finalement résolu:

Comme décrit dans ce post sur les légendes personnalisées , il est vraiment très facile de créer des légendes personnalisées. Ils sont également très faciles à connecter à Interface Builder, ils gardent les relations dans IB visibles et ne nécessitent pas beaucoup de support de la part des contrôleurs de vue source/destination de la segue.

La publication liée ci-dessus fournit du code iOS 4 pour remplacer le contrôleur de vue de dessus actuel sur la pile de navigationController par un nouveau à l'aide d'une animation par glissement de bout en bout.

Dans mon cas, je souhaitais un remplacement similaire, mais avec une transition FlipFromLeft. Je n'avais également besoin que d'une prise en charge pour iOS 5+. 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, maintenez la touche Ctrl enfoncée pour définir un autre type de séquence, faites-en une division personnalisée et tapez le nom de la classe de division personnalisée. Et le tour est joué!

3
Ryan Artecona

Je me suis battu avec celui-ci pendant un long moment, et un de mes problèmes est énuméré ici , je ne suis pas sûr si vous avez eu ce problème. Mais voici ce que je recommanderais si cela doit fonctionner avec iOS 4.

Tout d'abord, créez une nouvelle classe NavigationController. C’est là que nous allons faire tout le sale boulot - d’autres classes pourront appeler "proprement" des méthodes d’instance comme pushViewController: et autres choses de ce genre. 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 de contrôleurs de vue enfant servira de magasin pour tous les contrôleurs de vue de notre pile. Nous transférerions automatiquement tout le code de rotation et de redimensionnement de la vue de NavigationController vers currentController.

Maintenant, dans notre implémentation:

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

Maintenant, vous pouvez implémenter votre propre pushViewController:, popViewController et autres, en utilisant ces appels de méthode.

Bonne chance et j'espère que ça aide!

2
aopsfan