viewWillDisappear: determina se il controller di visualizzazione viene visualizzato o mostra un controller di visualizzazione secondaria


134

Sto lottando per trovare una buona soluzione a questo problema. Nel -viewWillDisappear:metodo di un controller di visualizzazione , devo trovare un modo per determinare se è perché un controller di visualizzazione viene inserito nello stack del controller di navigazione o se è perché il controller di visualizzazione sta scomparendo perché è stato visualizzato.

Al momento sto impostando bandiere come isShowingChildViewControllerma sta diventando abbastanza complicato. L'unico modo in cui penso di poterlo rilevare è nel -deallocmetodo.

Risposte:


228

È possibile utilizzare quanto segue.

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];
  NSArray *viewControllers = self.navigationController.viewControllers;
  if (viewControllers.count > 1 && [viewControllers objectAtIndex:viewControllers.count-2] == self) {
    // View is disappearing because a new view controller was pushed onto the stack
    NSLog(@"New view controller was pushed");
  } else if ([viewControllers indexOfObject:self] == NSNotFound) {
    // View is disappearing because it was popped from the stack
    NSLog(@"View controller was popped");
  }
}

Ciò è ovviamente possibile perché lo stack del controller di visualizzazione di UINavigationController (esposto attraverso la proprietà viewControllers) è stato aggiornato al momento della chiamata di viewWillDisappear.


2
Perfetto! Non so perché non ci abbia pensato! Immagino di non aver pensato che lo stack sarebbe stato modificato fino a quando i metodi di scomparsa non fossero stati chiamati! Grazie :-)
Michael Waterfall,

1
Ho appena provato a eseguire la stessa cosa ma dentro viewWillAppeare sembrerebbe che se il controller della vista viene rivelato dal fatto che venga spinto o che qualcosa al di sopra di esso venga espulso, l'array viewControllers è lo stesso in entrambi i modi! Qualche idea?
Michael Waterfall,

Dovrei anche notare che il controller di visualizzazione è persistente per tutta la vita dell'app, quindi non posso eseguire le mie azioni viewDidLoadperché viene chiamato solo una volta! Hmm, difficile!
Michael Waterfall,

4
@Sbrocket c'è un motivo che non hai fatto ![viewControllers containsObject:self]invece di [viewControllers indexOfObject:self] == NSNotFound? Scelta di stile?
zekel,

24
Questa risposta è obsoleta da iOS 5. Il -isMovingFromParentViewControllermetodo indicato di seguito consente di verificare se la vista viene visualizzata in modo esplicito.
Grahamparks,

136

Penso che il modo più semplice sia:

 - (void)viewWillDisappear:(BOOL)animated
{
    if ([self isMovingFromParentViewController])
    {
        NSLog(@"View controller was popped");
    }
    else
    {
        NSLog(@"New view controller was pushed");
    }
    [super viewWillDisappear:animated];
}

Swift:

override func viewWillDisappear(animated: Bool)
{
    if isMovingFromParent
    {
        print("View controller was popped")
    }
    else
    {
        print("New view controller was pushed")
    }
    super.viewWillDisappear(animated)
}

A partire da iOS 5 questa è la risposta, forse controlla anche isBeingDismissed
d370urn3ur

4
Per iOS7 devo controllare [self.navigationController.viewControllers indexOfObject: self] == NSNotFound di nuovo perché anche lo sfondo dell'app passerà questo test ma non rimuoverà lo stack di navigazione.
Eric Chen,

3
Apple ha fornito un modo documentato per farlo - stackoverflow.com/a/33478133/385708
Shyam Bhat,

Il problema con l'utilizzo di viewWillDisappear è che è possibile che il controller venga estratto dallo stack mentre la vista è già scomparsa. Ad esempio, un altro viewcontroller potrebbe essere spinto in cima allo stack e quindi chiamare popToRootViewControllerAnimated bypassing viewWillDisappear su quelli nel mezzo.
John K,

Supponiamo di avere due controller (root vc e un altro push) sul tuo stack di navigazione. Quando il terzo viene premuto view, WillDisappear viene chiamato sul secondo la cui vista sta per scomparire, giusto? Quindi quando si passa al controller della vista radice (pop il terzo e il secondo) viewWillDisappear viene chiamato sul terzo, cioè l'ultimo vc nello stack perché la vista è in cima e sta per scomparire in questo momento e la vista del secondo era già scomparsa. Ecco perché questo metodo si chiama viewWillDisappear e non viewControllerWillBePopped.
RTasche,

61

Dalla documentazione di Apple in UIViewController.h:

"Questi quattro metodi possono essere utilizzati nei richiami di aspetto di un controller di visualizzazione per determinare se viene presentato, eliminato o aggiunto o rimosso come controller di visualizzazione figlio. Ad esempio, un controller di visualizzazione può verificare se sta scomparendo perché è stato eliminato o spuntato chiedendosi a suo avviso WillDisappear: metodo controllando l'espressione ([self isBeingDismissed] || [self isMovingFromParentViewController]) "

- (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);

- (BOOL)isBeingDismissed NS_AVAILABLE_IOS(5_0);

- (BOOL)isMovingToParentViewController NS_AVAILABLE_IOS(5_0);

- (BOOL)isMovingFromParentViewController NS_AVAILABLE_IOS(5_0);

Quindi sì, l'unico modo documentato per farlo è il seguente:

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if ([self isBeingDismissed] || [self isMovingFromParentViewController]) {
    }
}

Versione Swift 3:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    if self.isBeingDismissed || self.isMovingFromParentViewController { 
    }
}

19

Se si vuole solo sapere se il vostro punto di vista è sempre spuntato, ho appena scoperto che self.navigationControllerè nilin viewDidDisappear, quando viene rimossa dalla pila di controllori. Quindi questo è un semplice test alternativo.

(Questo lo scopro dopo aver provato ogni sorta di altre contorsioni. Sono sorpreso che non vi sia alcun protocollo del controller di navigazione per registrare un controller di visualizzazione per ricevere notifiche sui pop. Non è possibile utilizzarlo UINavigationControllerDelegateperché in realtà funziona davvero sul display.)


16

Swift 4

override func viewWillDisappear(_ animated: Bool)
    {
        super.viewWillDisappear(animated)
        if self.isMovingFromParent
        {
            //View Controller Popped
        }
        else
        {
            //New view controller pushed
        }
    }

6

In Swift:

 override func viewWillDisappear(animated: Bool) {
    if let navigationController = self.navigationController {
        if !contains(navigationController.viewControllers as! Array<UIViewController>, self) {
        }
    }

    super.viewWillDisappear(animated)

}

Assicurati di usare come! invece di as
dfmuir,

2

Trovo che la documentazione di Apple su questo sia difficile da capire. Questa estensione aiuta a vedere gli stati ad ogni navigazione.

extension UIViewController {
    public func printTransitionStates() {
        print("isBeingPresented=\(isBeingPresented)")
        print("isBeingDismissed=\(isBeingDismissed)")
        print("isMovingToParentViewController=\(isMovingToParentViewController)")
        print("isMovingFromParentViewController=\(isMovingFromParentViewController)")
    }
}

1

Questa domanda è piuttosto vecchia ma l'ho vista per caso, quindi voglio pubblicare le migliori pratiche (afaik)

puoi semplicemente fare

if([self.navigationController.viewControllers indexOfObject:self]==NSNotFound)
 // view controller popped
}

1

Questo vale per iOS7 , non ho idea se si applica ad altri. Da quello che so, nella viewDidDisappearvista è già spuntato. Ciò significa che quando esegui self.navigationController.viewControllersuna query otterrai un nil. Quindi controlla se è zero.

TL; DR

 - (void)viewDidDisappear:(BOOL)animated
 {
    [super viewDidDisappear:animated];
    if (self.navigationController.viewControllers == nil) {
        // It has been popped!
        NSLog(@"Popped and Gone");
    }
 }

1

Segues può essere un modo molto efficace di gestire questo problema in iOS 6+. Se hai specificato l'identificatore segue il identificatore in Interface Builder, puoi verificarlo prepareForSegue.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"LoginSegue"]) {
       NSLog(@"Push");
       // Do something specific here, or set a BOOL indicating
       // a push has occurred that will be checked later
    }
}

1

Grazie @Bryan Henry, lavora ancora in Swift 5

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if let controllers = navigationController?.children{
            if controllers.count > 1, controllers[controllers.count - 2] == self{
                // View is disappearing because a new view controller was pushed onto the stack
                print("New view controller was pushed")
            }
            else if controllers.firstIndex(of: self) == nil{
                // View is disappearing because it was popped from the stack
                print("View controller was popped")
            }
        }

    }

-1

Suppongo che intendi dire che la tua vista viene spostata nello stack del controller di navigazione spingendo una nuova vista quando dici spinta nello stack. Suggerirei di utilizzare il viewDidUnloadmetodo per aggiungere una NSLogdichiarazione per scrivere qualcosa sulla console in modo da poter vedere cosa sta succedendo, potresti voler aggiungere un NSLoga viewWillDissappeer.


-1

Ecco una categoria per realizzare la stessa cosa della risposta di sbrocket:

Intestazione:

#import <UIKit/UIKit.h>

@interface UIViewController (isBeingPopped)

- (BOOL) isBeingPopped;

@end

Fonte:

#import "UIViewController+isBeingPopped.h"

@implementation UIViewController (isBeingPopped)

- (BOOL) isBeingPopped {
    NSArray *viewControllers = self.navigationController.viewControllers;
    if (viewControllers.count > 1 && [viewControllers objectAtIndex:viewControllers.count-2] == self) {
        return NO;
    } else if ([viewControllers indexOfObject:self] == NSNotFound) {
        return YES;
    }
    return NO;
}

@end
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.