Animare il cambio di controller di visualizzazione senza utilizzare stack di controller di navigazione, visualizzazioni secondarie o controller modali?


142

I NavigationController hanno stack ViewController da gestire e transizioni di animazione limitate.

L'aggiunta di un controller di visualizzazione come visualizzazione secondaria a un controller di visualizzazione esistente richiede il passaggio di eventi al controller di visualizzazione secondaria, il che è un problema da gestire, carico di piccoli fastidi e in generale sembra un brutto hack durante l'implementazione (Apple consiglia anche di non facendo questo).

Presentare di nuovo un controller di visualizzazione modale posiziona un controller di visualizzazione in cima a un altro e, sebbene non abbia i problemi di passaggio dell'evento sopra descritti, in realtà non "scambia" il controller di visualizzazione, lo impila.

Gli storyboard sono limitati a iOS 5 e sono quasi ideali, ma non possono essere utilizzati in tutti i progetti.

Qualcuno può presentare un ESEMPIO DI CODICE SOLIDO su un modo per cambiare i controller di visualizzazione senza le limitazioni di cui sopra e consentire transizioni animate tra di loro?

Un esempio ravvicinato, ma nessuna animazione: come utilizzare più controller di visualizzazione personalizzati iOS senza un controller di navigazione

Modifica: l'uso del controller di navigazione va bene, ma devono esserci stili di transizione animati (non semplicemente effetti di diapositiva), il controller di visualizzazione mostrato deve essere completamente sostituito (non impilato). Se il controller della seconda vista deve rimuovere un altro controller della vista dallo stack, non è sufficientemente incapsulato.

Modifica 2: iOS 4 dovrebbe essere il sistema operativo di base per questa domanda, avrei dovuto chiarirlo nel menzionare gli storyboard (sopra).


1
È possibile eseguire transizioni di animazione personalizzate con un controller di navigazione. Se questo è accettabile, rimuovi quel vincolo dalla tua domanda e posterò un esempio di codice.
Richard Brightwell,

@Richard se salta la seccatura di gestire lo stack e accetta diversi stili di transizione animati tra i controller di visualizzazione, l'utilizzo del controller di navigazione va bene!
TigerCoding

Ok bene. Sono diventato impaziente e ho pubblicato il codice. Provaci. Per me va bene.
Richard Brightwell,

@RichardBrightwell qui hai detto che si potevano fare transizioni di animazione personalizzate tra i controller di visualizzazione usando un controller di navigazione ... come? Puoi pubblicare un esempio? Grazie.
Duck,

Risposte:


108

EDIT: nuova risposta che funziona con qualsiasi orientamento. La risposta originale funziona solo quando l'interfaccia è in orientamento verticale. Si tratta di animazioni di transizione della vista b / c che sostituiscono una vista con una vista diversa che deve avvenire con viste almeno un livello al di sotto della prima vista aggiunta alla finestra (ad eswindow.rootViewController.view.anotherView.).

Ho implementato una semplice classe contenitore che ho chiamato TransitionController. Puoi trovarlo su https://gist.github.com/1394947 .

Per inciso, preferisco l'implementazione in una classe separata b / c che è più facile da riutilizzare. Se non lo desideri, puoi semplicemente implementare la stessa logica direttamente nel delegato dell'app eliminando la necessità della TransitionControllerclasse. La logica di cui avresti bisogno sarebbe la stessa comunque.

Usalo come segue:

Nel delegato dell'app

// 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;
}

Per passare a un nuovo controller di visualizzazione da qualsiasi controller di visualizzazione

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

EDIT: risposta originale di seguito - funziona solo per l'orientamento del portait

Ho fatto le seguenti ipotesi per questo esempio:

  1. Hai un controller di visualizzazione assegnato come rootViewControllerdella finestra

  2. Quando si passa a una nuova vista, si desidera sostituire il viewController corrente con viewController che possiede la nuova vista. In qualsiasi momento, solo l'attuale viewController è attivo (ad es. Allocato).

Il codice può essere facilmente modificato per funzionare in modo diverso, il punto chiave è la transizione animata e il controller a vista singola. Assicurati di non conservare un controller di visualizzazione in nessun luogo al di fuori dell'assegnazione a window.rootViewController.

Codice per animare la transizione nel delegato dell'app

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

Esempio di utilizzo in un controller di visualizzazione

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

1
Sì molto carino! Non solo fa il trucco, ma è un esempio di codice molto semplice e pulito. Grazie molto!
TigerCoding

La sua causa non ti crea problemi nel paesaggio? Inoltre: questo attiva i metodi willAppear e didAppear dei viewController?
Felix Lamouroux,

1
So che le chiamate apparenti sono state fatte perché le ho registrate. Inoltre non vedo perché ciò influirebbe sui cambiamenti di orientamento. Puoi spiegare perché pensi che sarebbe?
TigerCoding il

1
Ottima soluzione @XJones, funziona abbastanza bene nella mia app per iPad, tranne per un problema che sto affrontando, dopo la prima inizializzazione transVC = [TransitionController ... initWithViewController: aUINavCtrlObj]; window.rootVC = transVC; la vista in aUINavCtrlObj è riempita di 20px dall'alto (ovvero i 20 pixel inferiori sono fuori dallo schermo (UIWindow) in tutti gli orientamenti), ma dopo aver fatto [transVC transizioneToViewController: un altro VC] il riempimento è sparito. Ho provato wantsFullScreenLayout = NO nel loadView di TransitionController, ciò che fa è aggiungere un'area nera di 20 px appena sotto statusBar.
Abduliam Rehmanius,

1
@AbduliamRehmanius: ho avuto lo stesso problema. L'ho risolto cambiando la riga 25 di TransitionController.ma UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];, ma l'ho usato solo sulla nuovissima versione di iOS, quindi prova attentamente.
Phil Calvin,

67

Puoi utilizzare il nuovo sistema di contenimento viewController di Apple. Per informazioni più approfondite, guarda il video della sessione del WWDC 2011 "Implementing UIViewControllerContainment".

Nuovo per iOS5, il UIViewControllercontenimento ti consente di avere un controllore della vista padre e un certo numero di controllori della vista figlio contenuti al suo interno. Ecco come funziona UISplitViewController. In questo modo puoi impilare i controller di visualizzazione in un genitore, ma per la tua particolare applicazione stai semplicemente utilizzando il genitore per gestire la transizione da un viewController visibile a un altro. Questo è il modo approvato da Apple di fare le cose e l'animazione da un punto di vista di un bambino Il controller è indolore. Inoltre puoi usare tutte le diverse UIViewAnimationOptiontransizioni!

Inoltre, con UIViewContainment, non devi preoccuparti, a meno che tu non voglia, della confusione della gestione dei controlli bambini viewController durante gli eventi di orientamento. È possibile semplicemente utilizzare quanto segue per assicurarsi che parentViewController inoltri gli eventi di rotazione ai viewController secondari.

- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{
    return YES;
}

Puoi fare quanto segue o simile nel metodo viewDidLoad del tuo genitore per configurare il primo childViewController:

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

quindi quando devi cambiare il viewController figlio, chiami qualcosa come segue all'interno del viewController padre:

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

Ho pubblicato un progetto di esempio completo qui: https://github.com/toolmanGitHub/stackedViewControllers . Questo altro progetto mostra come utilizzare il UIViewControllercontenimento su alcuni tipi di controlli di input che non occupano l'intero schermo. In bocca al lupo


1
Un grande esempio Grazie per aver dedicato del tempo alla pubblicazione del codice. D'altra parte, è limitato solo a iOS 5. Come menzionato nella domanda: "[Storyboard] sono limitati a iOS 5 e sono quasi ideali, ma non possono essere utilizzati in tutti i progetti". Considerando che una grande percentuale (circa il 40%?) Dei clienti utilizza ancora iOS 4, l'obiettivo è fornire qualcosa che funzioni in iOS 4 e versioni successive.
TigerCoding

Non dovresti chiamare [self.currentViewController willMoveToParentViewController:nil];prima della transizione?
Felix Lamouroux,

@FelixLam: per i documenti sul contenimento di UIViewController, si chiamerà willMoveToParentViewController solo se si sovrascrive il metodo addChildViewController. In questo esempio lo chiamo ma non prevale.
timthetoolman,

2
Nella demo del WWDC mi sembra di ricordare che l'hanno chiamato prima di chiamare l'avvio della transizione, perché la transizione non implica che l'attuale CV si sposterà a zero. Nel caso di un UITabBarController la transizione non cambierebbe alcun genitore di vc. La rimozione dal genitore chiama didMoveToParentViewController: zero, ma non è possibile ... chiamare. IMHO
Felix Lamouroux,

7

OK, so che la domanda dice senza usare un controller di navigazione, ma nessun motivo per non farlo. OP non rispondeva ai commenti in tempo per farmi addormentare. Non votarmi. :)

Ecco come visualizzare l'attuale controller di visualizzazione e passare a un nuovo controller di visualizzazione utilizzando un controller di navigazione:

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

Questo stack non visualizza i controller?
TigerCoding

1
Sì, perché stiamo utilizzando un controller di navigazione. Tuttavia, aggira la limitazione sul tipo di transizioni che puoi eseguire, che pensavo fosse il nucleo della tua domanda.
Richard Brightwell,

Chiudi, ma uno dei maggiori problemi è che ci sono controller a più viste da gestire in pila. Sto cercando il modo di cambiare completamente i controller di visualizzazione. =)
TigerCoding

Ah. Potrei avere anche qualcosa del genere ... dammi un minuto. In caso contrario, eliminerò questa risposta.
Richard Brightwell,

Ok, ho messo insieme due diversi pezzi di codice. Penso che questo farà quello che vuoi.
Richard Brightwell,

3

Dato che ho appena riscontrato questo esatto problema e ho provato le varianti di tutte le risposte preesistenti a un successo limitato, posterò come alla fine l'ho risolto:

Come descritto in questo post sui follower personalizzati , in realtà è davvero facile creare follower personalizzati. Sono anche semplicissimi da collegare in Interface Builder, mantengono visibili le relazioni in IB e non richiedono molto supporto da parte dei controller della vista sorgente / destinazione dei seguenti.

Il post collegato sopra fornisce il codice iOS 4 per sostituire l'attuale controller della vista dall'alto nello stack di NavigationController con uno nuovo usando un'animazione slide-in-from-top.

Nel mio caso, volevo che succedesse un rimpiazzo simile, ma con una FlipFromLefttransizione. Avevo anche bisogno solo del supporto per iOS 5+. Codice:

Da RAFlipReplaceSegue.h:

#import <UIKit/UIKit.h>

@interface RAFlipReplaceSegue : UIStoryboardSegue
@end

Da 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

Ora, tieni premuto il tasto Ctrl per impostare qualsiasi altro tipo di seguito, quindi impostalo come segue personalizzata e digita il nome della classe segue personalizzata, e voilà!


C'è un modo per farlo programmaticamente senza storyboard?
zakdances,

Per quanto ne so, per usare un seguito devi definirlo e dargli un identificatore in uno storyboard. È possibile richiamare un codice segue con il –performSegueWithIdentifier:sender:metodo UIViewController .
Ryan Artecona,

1
Non si dovrebbe mai chiamare viewDidXXX e viewWillXXX direttamente.
Ben Sinclair,

2

Ho lottato con questo per molto tempo e uno dei miei problemi è elencato qui , non sono sicuro che tu abbia avuto quel problema. Ma ecco cosa consiglierei se dovesse funzionare con iOS 4.

Innanzitutto, crea una nuova NavigationControllerclasse. Qui è dove faremo tutto il lavoro sporco - altre classi saranno in grado di chiamare in modo "pulito" metodi di istanza come pushViewController:e simili. Nel tuo .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;

L'array dei controller di visualizzazione figlio fungerà da archivio per tutti i controller di visualizzazione nel nostro stack. Inoltreremo automaticamente tutta la rotazione e il ridimensionamento del codice dalla NavigationControllervista a currentController.

Ora, nella nostra implementazione:

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

Ora è possibile implementare il proprio personalizzato pushViewController:, popViewControllere quali, utilizzando queste chiamate di metodo.

Buona fortuna e spero che questo ti aiuti!


Ancora una volta dobbiamo ricorrere all'aggiunta di controller di visualizzazione come visualizzazioni secondarie di controller di visualizzazione esistenti. Certo, questo è ciò che fa il controller di navigazione esistente, ma significa che fondamentalmente dobbiamo riscriverlo e tutti i suoi metodi. In realtà, dovremmo evitare di distribuire viewWillAppear e metodi simili. Sto cominciando a pensare che non esiste un modo pulito per farlo. Tuttavia, ti ringrazio per aver dedicato tempo e fatica!
TigerCoding

Penso che, con questo sistema di aggiunta e rimozione dei controller di visualizzazione in base alle esigenze, questa soluzione ti impedisca di inoltrare tali metodi.
aopsfan,

Sei sicuro? Prima scambiavo i controller di visualizzazione in modo simile e dovevo sempre inoltrare i messaggi. Puoi confermare diversamente?
TigerCoding il

No, non sono sicuro, ma presumo che, fino a quando si rimuove vista dei controllori View come scompaiono, e aggiungerli come lo fanno apparire, che dovrebbe automaticamente grilletto viewWillAppear, viewDidAppeare così via.
aopsfan,

-1
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UINavigationController *viewController = (UINavigationController *)[storyboard instantiateViewControllerWithIdentifier:@"storyBoardIdentifier"];
viewController.modalTransitionStyle = UIModalTransitionStylePartialCurl;
[self presentViewController:viewController animated:YES completion:nil];

Prova questo codice.


Questo codice fornisce la transizione da un controller di visualizzazione a un altro controller di visualizzazione che ha un controller di navigazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.