Come posso aprire una vista da un UINavigationController e sostituirla con un'altra in un'unica operazione?


84

Ho un'applicazione in cui devo rimuovere una vista dallo stack di un UINavigationController e sostituirla con un'altra. La situazione è che la prima visualizzazione crea un elemento modificabile e quindi si sostituisce con un editor per l'elemento. Quando eseguo la soluzione ovvia nella prima vista:

MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

[self retain];
[self.navigationController popViewControllerAnimated: NO];
[self.navigationController pushViewController: mevc animated: YES];
[self release];

Ottengo un comportamento molto strano. Di solito viene visualizzata la visualizzazione dell'editor, ma se provo a utilizzare il pulsante Indietro sulla barra di navigazione ottengo schermate aggiuntive, alcune vuote e alcune semplicemente incasinate. Anche il titolo diventa casuale. È come se la pila di navigazione fosse completamente chiusa.

Quale sarebbe un approccio migliore a questo problema?

Grazie, Matt

Risposte:


137

Ho scoperto che non è affatto necessario modificare manualmente la viewControllersproprietà. Fondamentalmente ci sono 2 cose complicate su questo.

  1. self.navigationControllerrestituirà nilse selfnon è attualmente nello stack del controller di navigazione. Quindi salvalo in una variabile locale prima di perdere l'accesso ad essa.
  2. Devi retain(e correttamente release) selfo l'oggetto che possiede il metodo in cui ti trovi verrà deallocato, causando stranezza.

Una volta che hai fatto quella preparazione, fai scoppiare e premi normalmente. Questo codice sostituirà immediatamente il controller superiore con un altro.

// locally store the navigation controller since
// self.navigationController will be nil once we are popped
UINavigationController *navController = self.navigationController;

// retain ourselves so that the controller will still exist once it's popped off
[[self retain] autorelease];

// Pop this controller and replace with another
[navController popViewControllerAnimated:NO];
[navController pushViewController:someViewController animated:NO];

In quest'ultima riga, se cambi animatedin YES, il nuovo schermo si animerà effettivamente e il controller che hai appena aperto si animerà. Sembra piuttosto carino!


brillante! soluzione molto migliore
emmby

Eccezionale. Anche se non avevo bisogno di chiamare [[self keep] autorelease], funziona comunque bene.
iamj4de

4
Forse un'aggiunta ovvia, ma puoi quindi inserire il codice sopra in un blocco di animazione per animare la transizione: [UIView beginAnimations: @ "View Flip" context: nil]; [UIView setAnimationDuration: 0.80]; [UIView setAnimationCurve: UIViewAnimationCurveEaseInOut]; [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView: navController.view cache: NO]; [navController pushViewController: newController animated: YES]; [UIView commitAnimations];
Martin

11
Funziona alla grande con ARC semplicemente rimuovendo la linea di mantenimento / rilascio automatico.
Ian Terrell

2
@TomerPeled Sì, questa risposta ha quasi 5 anni ... Penso che fosse il caso di iOS 3. Le API sono cambiate abbastanza che non sono più sicuro che sia più la risposta migliore.
Alex Wayne

56

Il seguente approccio mi sembra più carino e funziona bene anche con ARC:

UIViewController *newVC = [[UIViewController alloc] init];
// Replace the current view controller
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
[viewControllers removeLastObject];
[viewControllers addObject:newVC];
[[self navigationController] setViewControllers:viewControllers animated:YES];

1
@LukeRogers, questo mi provoca il seguente avviso: Completamento di una transizione di navigazione in uno stato imprevisto. L'albero delle sottoview della barra di navigazione potrebbe essere danneggiato. Qualche modo per sopprimerlo?
zaitsman

Usando questa soluzione, sovrascrivi il popover. E per essere mostrato in DetailView, il tuo codice dovrebbe essere:if(indexPath.row == 0){UIViewController *newVC = [[UIViewController alloc] init];newVC = [self.storyboard instantiateViewControllerWithIdentifier:@"Item1VC"]; NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[_detailViewController.navigationController viewControllers]]; [viewControllers removeLastObject];[viewControllers addObject:newVC]; [_detailViewController.navigationController setViewControllers:viewControllers animated:YES];}
LAOMUSIC ARTS

Quello che stavo cercando.
JERC

9

Per esperienza, dovrai armeggiare direttamente con la viewControllersproprietà di UINavigationController . Qualcosa di simile dovrebbe funzionare:

MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

[[self retain] autorelease];
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
self.navigationController.viewControllers = controllers;
[self.navigationController pushViewController:mevc animated: YES];

Nota: ho modificato la conservazione / rilascio in una conservazione / rilascio automatico poiché è generalmente più robusta: se si verifica un'eccezione tra la conservazione / rilascio, si perde automaticamente, ma il rilascio automatico si occupa di questo.


7

Dopo molti sforzi (e modificando il codice di Kevin), ho finalmente capito come farlo nel controller della vista che viene estratto dallo stack. Il problema che stavo avendo era che self.navigationController stava restituendo zero dopo aver rimosso l'ultimo oggetto dall'array dei controller. Penso che fosse dovuto a questa riga nella documentazione per UIViewController sul metodo di istanza navigationController "Restituisce un controller di navigazione solo se il controller di visualizzazione è nel suo stack."

Penso che una volta rimosso il controller di visualizzazione corrente dallo stack, il suo metodo navigationController restituirà nil.

Ecco il codice modificato che funziona:

UINavigationController *navController = self.navigationController;
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
navController.viewControllers = controllers;
[navController pushViewController:mevc animated: YES];

Questo mi dà un intero nero!
ARTI LAOMUSICHE

4

Grazie, questo era esattamente quello di cui avevo bisogno. Lo inserisco anche in un'animazione per ottenere l'arricciatura della pagina:

        MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

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

    [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration: 0.7];
    [UIView setAnimationTransition:<#UIViewAnimationTransitionCurlDown#> forView:navController.view cache:NO];

    [navController popViewControllerAnimated:NO];
    [navController pushViewController:mevc animated:NO];

    [UIView commitAnimations];

La durata di 0.6 è veloce, buona per 3GS e più recenti, 0.8 è ancora un po 'troppo veloce per 3G ..

Johan


Il tuo codice è esattamente quello che ho usato, fantastico! Grazie. Una nota: con la transizione del ricciolo di pagina ho ottenuto un artificio bianco nella parte inferiore della vista (chissà perché) ma con il flip ha funzionato bene. Comunque, questo è un codice carino e compatto!
David H

3

Se vuoi mostrare qualsiasi altro controller di visualizzazione da popToRootViewController, devi fare quanto segue:

         UIViewController *newVC = [[WelcomeScreenVC alloc] initWithNibName:@"WelcomeScreenVC" bundle:[NSBundle mainBundle]];
            NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
            [viewControllers removeAllObjects];
            [viewControllers addObject:newVC];
            [[self navigationController] setViewControllers:viewControllers animated:NO];

Ora, tutto lo stack precedente verrà rimosso e il nuovo stack verrà creato con il rootViewController richiesto.


1

Ho dovuto fare una cosa simile di recente e ho basato la mia soluzione sulla risposta di Michaels. Nel mio caso ho dovuto rimuovere due View Controller dallo Stack di navigazione e quindi aggiungere un nuovo View Controller. Chiamando

[controllers removeLastObject];
due volte, ha funzionato bene nel mio caso.

UINavigationController *navController = self.navigationController;

// retain ourselves so that the controller will still exist once it's popped off
[[self retain] autorelease];

searchViewController = [[SearchViewController alloc] init];    
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];

[controllers removeLastObject];
// In my case I want to go up two, then push one..
[controllers removeLastObject];
navController.viewControllers = controllers;

NSLog(@"controllers: %@",controllers);
controllers = nil;

[navController pushViewController:searchViewController animated: NO];


1

Questo UINavigationControllermetodo di istanza potrebbe funzionare ...

Visualizza i controller di visualizzazione fino a quando il controller di visualizzazione specificato è il controller di visualizzazione superiore, quindi aggiorna la visualizzazione.

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated

1

Ecco un altro approccio che non richiede la manipolazione diretta dell'array viewControllers. Controlla se il controller è già stato aperto, in tal caso spingilo.

TasksViewController *taskViewController = [[TasksViewController alloc] initWithNibName:nil bundle:nil];

if ([navigationController.viewControllers indexOfObject:taskViewController] == NSNotFound)
{
    [navigationController pushViewController:taskViewController animated:animated];
}
else
{
    [navigationController popToViewController:taskViewController animated:animated];
}

1
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
    for(int i=0;i<controllers.count;i++){
       [controllers removeLastObject];
    }
 self.navigationController.viewControllers = controllers;

questo provoca un avviso per me nella console: completamento di una transizione di navigazione in uno stato imprevisto. L'albero delle sottoview della barra di navigazione potrebbe essere danneggiato. Qualche modo per sopprimerlo?
zaitsman

1

Il mio modo preferito per farlo è con una categoria su UINavigationController. Quanto segue dovrebbe funzionare:

UINavigationController + Helpers.h #import

@interface UINavigationController (Helpers)

- (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller;

@end

UINavigationController + Helpers.m
#import "UINavigationController + Helpers.h"

@implementation UINavigationController (Helpers)

- (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller {
    UIViewController* topController = self.viewControllers.lastObject;
    [[topController retain] autorelease];
    UIViewController* poppedViewController = [self popViewControllerAnimated:NO];
    [self pushViewController:controller animated:NO];
    return poppedViewController;
}

@end

Quindi dal controller della vista, puoi sostituire la vista dall'alto con una nuova in questo modo:

[self.navigationController replaceTopViewControllerWithViewController: newController];

0

Puoi controllare con l'array dei controller di visualizzazione di navigazione che ti vengono forniti tutti i controller di visualizzazione aggiunti nello stack di navigazione. Usando quell'array puoi tornare indietro a uno specifico controller di visualizzazione.


0

Per monotouch / xamarin IOS:

all'interno della classe UISplitViewController;

UINavigationController mainNav = this._navController; 
//List<UIViewController> controllers = mainNav.ViewControllers.ToList();
mainNav.ViewControllers = new UIViewController[] { }; 
mainNav.PushViewController(detail, true);//to have the animation

0

In alternativa,

Puoi usare categoryper evitare self.navigationControllerdi essere nildopopopViewControllerAnimated

basta premere e spingere, è facile da capire, non è necessario accedere viewControllers...

Nel tuo ViewController


0

Non esattamente la risposta, ma potrebbe essere di aiuto in alcuni scenari (il mio ad esempio):

Se hai bisogno di far apparire il viewcontroller C e andare su B (fuori dallo stack) invece di A (quello sotto C), è possibile spingere B prima di C, e avere tutti e 3 in pila. Mantenendo la spinta B invisibile e scegliendo se far apparire solo C o C e B insieme, puoi ottenere lo stesso effetto.

problema iniziale A -> C (voglio far apparire C e mostrare B, fuori dallo stack)

possibile soluzione A -> B (spinto invisibile) -> C (quando apro C, scelgo di mostrare B o anche di visualizzarlo)


0

Uso questa soluzione per mantenere l'animazione.

[self.navigationController pushViewController:controller animated:YES];
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newControllers removeObject:newControllers[newControllers.count - 2]];
[self.navigationController setViewControllers:newControllers];
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.