viewWillDisappear: Określa, czy kontroler widoku jest otwierany, czy też wyświetla kontroler widoku podrzędnego

134

Staram się znaleźć dobre rozwiązanie tego problemu. W -viewWillDisappear:metodzie kontrolera widoku muszę znaleźć sposób na ustalenie, czy dzieje się tak, ponieważ kontroler widoku jest wypychany na stos kontrolera nawigacji, czy też dlatego, że kontroler widoku znika, ponieważ został wyskakujący.

W tej chwili ustawiam flagi takie jak isShowingChildViewControllerale robi się to dość skomplikowane. Myślę, że jedyny sposób, w jaki mogę to wykryć, to -deallocmetoda.

Michael Waterfall
źródło

Odpowiedzi:

228

Możesz użyć następujących.

- (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");
  }
}

Jest to oczywiście możliwe, ponieważ stos kontrolera widoku UINavigationControllera (ujawniony za pośrednictwem właściwości viewControllers) został zaktualizowany do czasu wywołania metody viewWillDisappear.

Bryan Henry
źródło
2
Idealny! Nie wiem, dlaczego o tym nie pomyślałem! Myślę, że nie sądziłem, że stos zostanie zmieniony, dopóki nie zostaną wywołane metody znikania! Dzięki :-)
Michael Waterfall
1
Właśnie próbowałem wykonać to samo, ale viewWillAppearwydaje się, że niezależnie od tego, czy kontroler widoku jest ujawniany przez wypychanie go, czy coś nad nim jest otwierane, tablica viewControllers jest taka sama w obu kierunkach! Jakieś pomysły?
Michael Waterfall
Powinienem również zauważyć, że kontroler widoku jest trwały przez cały okres istnienia aplikacji, więc nie mogę wykonywać moich działań, viewDidLoadponieważ jest wywoływany tylko raz! Hmm, podstępny!
Michael Waterfall
4
@Sbrocket Czy jest powód, dla którego nie zrobiłeś ![viewControllers containsObject:self]tego zamiast [viewControllers indexOfObject:self] == NSNotFound? Wybór stylu?
zekel
24
Ta odpowiedź jest przestarzała od czasu iOS 5. -isMovingFromParentViewControllerMetoda wspomniana poniżej umożliwia sprawdzenie, czy widok jest jawnie wyskakujący.
grahamparks
136

Myślę, że najłatwiej jest:

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

Szybki:

override func viewWillDisappear(animated: Bool)
{
    if isMovingFromParent
    {
        print("View controller was popped")
    }
    else
    {
        print("New view controller was pushed")
    }
    super.viewWillDisappear(animated)
}
RTasche
źródło
Od iOS 5 to jest odpowiedź, może również sprawdź isBeingDismissed
d370urn3ur
4
W przypadku iOS7 muszę ponownie sprawdzić [self.navigationController.viewControllers indexOfObject: self] == NSNotFound, ponieważ aplikacja w tle również przejdzie ten test, ale nie usunie self ze stosu nawigacji.
Eric Chen
3
Apple zapewnił udokumentowany sposób, aby to zrobić - stackoverflow.com/a/33478133/385708
Shyam Bhat
Problem z używaniem viewWillDisappear polega na tym, że możliwe jest wyskoczenie kontrolera ze stosu, podczas gdy widok już zniknął. Na przykład inny kontroler widoku można umieścić na szczycie stosu, a następnie wywołać popToRootViewControllerAnimated, pomijając metodę viewWillDisappear na środkowych.
John K
Załóżmy, że masz dwa kontrolery (root vc i drugi push) na swoim stosie nawigacyjnym. Kiedy trzeci jest popychany, wywoływany jest viewWillDisappear na drugim, którego widok zniknie, prawda? Więc kiedy przejdziesz do głównego kontrolera widoku (zdejmiesz trzeci i drugi) viewWillDisappear jest wywoływany na trzecim, tj. Ostatnim vc na stosie, ponieważ jego widok jest na górze i zniknie w tym momencie, a drugi widok już zniknął. Dlatego ta metoda nazywa się viewWillDisappear, a nie viewControllerWillBePopped.
RTasche,
61

Z dokumentacji firmy Apple w UIViewController.h:

„Te cztery metody mogą być używane w wywołaniach zwrotnych wyglądu kontrolera widoku, aby określić, czy jest prezentowany, odrzucany, dodawany lub usuwany jako kontroler widoku podrzędnego. Na przykład kontroler widoku może sprawdzić, czy znika, ponieważ został odrzucony lub pojawiło się, zadając sobie pytanie w swoim viewWillDisappear: metoda, sprawdzając wyrażenie ([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);

Więc tak, jedynym udokumentowanym sposobem na to jest następujący sposób:

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

Wersja Swift 3:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    if self.isBeingDismissed || self.isMovingFromParentViewController { 
    }
}
Shyam Bhat
źródło
18

Jeśli chcesz po prostu wiedzieć, czy widok jest coraz trzasnął, właśnie odkryła, że self.navigationControllerjest nilwviewDidDisappear , kiedy jest usuwany ze stosu kontrolerów. Więc to jest prosty alternatywny test.

(Odkryłem to po wypróbowaniu różnych innych wygięć. Jestem zaskoczony, że nie ma protokołu kontrolera nawigacji, który rejestrowałby kontroler widoku, który ma być powiadamiany o wyskakujących okienkach. Nie możesz go używać, UINavigationControllerDelegateponieważ faktycznie działa on na wyświetlaczu).

dk.
źródło
16

Szybki 4

override func viewWillDisappear(_ animated: Bool)
    {
        super.viewWillDisappear(animated)
        if self.isMovingFromParent
        {
            //View Controller Popped
        }
        else
        {
            //New view controller pushed
        }
    }
Umair
źródło
6

W Swift:

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

    super.viewWillDisappear(animated)

}
user754905
źródło
Upewnij się, że używasz jako! zamiast as
dfmuir
2

Uważam, że dokumentacja Apple na ten temat jest trudna do zrozumienia. To rozszerzenie pomaga zobaczyć stany przy każdej nawigacji.

extension UIViewController {
    public func printTransitionStates() {
        print("isBeingPresented=\(isBeingPresented)")
        print("isBeingDismissed=\(isBeingDismissed)")
        print("isMovingToParentViewController=\(isMovingToParentViewController)")
        print("isMovingFromParentViewController=\(isMovingFromParentViewController)")
    }
}
Norman
źródło
1

To pytanie jest dość stare, ale widziałem je przez przypadek, więc chcę opublikować najlepsze praktyki (afaik)

możesz po prostu zrobić

if([self.navigationController.viewControllers indexOfObject:self]==NSNotFound)
 // view controller popped
}
user1396236
źródło
1

Dotyczy to iOS7 , nie mam pojęcia, czy dotyczy to innych. Z tego co wiem, w viewDidDisappearwidoku już wyskoczył. Co oznacza, że ​​gdy zapytasz self.navigationController.viewControllers, otrzymasz plik nil. Więc po prostu sprawdź, czy to zero.

TL; DR

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

Segues może być bardzo skutecznym sposobem radzenia sobie z tym problemem w iOS 6+. Jeśli nadałeś konkretnemu segue identyfikator w Interface Builder, możesz go sprawdzić w 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
    }
}
Kyle Clegg
źródło
1

Dzięki @Bryan Henry, nadal działa w 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")
            }
        }

    }
dengST30
źródło
-1

Zakładam, że masz na myśli, że twój widok jest przesuwany w dół stosu kontrolera nawigacji przez wypychanie nowego widoku, gdy mówisz, że został on przeniesiony na stos. Sugerowałbym użycie viewDidUnloadmetody, aby dodać NSLoginstrukcję, aby zapisać coś na konsoli, abyś mógł zobaczyć, co się dzieje, możesz dodać NSLogdo viewWillDissappeer.

Aaron
źródło
-1

Oto kategoria, w której można osiągnąć to samo, co odpowiedź sbrocket:

Nagłówek:

#import <UIKit/UIKit.h>

@interface UIViewController (isBeingPopped)

- (BOOL) isBeingPopped;

@end

Źródło:

#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
bbrame
źródło