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.

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.


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)
}

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 { 
    }
}

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).


16

Szybki 4

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

6

W Swift:

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

    super.viewWillDisappear(animated)

}

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

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
}

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

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

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

    }

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


-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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.