viewWillDisappear: Determine se o controlador de exibição está sendo exibido ou está mostrando um controlador de subvisualização

134

Estou lutando para encontrar uma boa solução para esse problema. No -viewWillDisappear:método de um controlador de exibição , preciso encontrar uma maneira de determinar se é porque um controlador de exibição está sendo empurrado para a pilha do controlador de navegação ou se é porque o controlador de exibição está desaparecendo porque foi acionado.

No momento, estou definindo sinalizadores como, isShowingChildViewControllermas está ficando bastante complicado. A única maneira que acho que posso detectá-lo é no -deallocmétodo.

Michael Waterfall
fonte

Respostas:

228

Você pode usar o seguinte.

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

Obviamente, isso é possível porque a pilha do controlador de exibição do UINavigationController (exposta pela propriedade viewControllers) foi atualizada no momento em que o viewWillDisappear é chamado.

Bryan Henry
fonte
2
Perfeito! Não sei por que não pensei nisso! Acho que não achei que a pilha fosse alterada até que os métodos de desaparecimento fossem chamados! Obrigado :-)
Michael Waterfall
1
Eu apenas estava tentando executar a mesma coisa, mas viewWillAppearparece que, se o controlador de exibição está sendo revelado por ser pressionado ou se algo acima está sendo acionado, o array viewControllers é o mesmo nos dois sentidos! Alguma ideia?
Michael Waterfall
Também devo observar que o controlador de exibição é persistente durante a vida útil do aplicativo, portanto não posso executar minhas ações viewDidLoad, pois é chamado apenas uma vez! Hmm, complicado!
Michael Waterfall
4
@Sbrocket há uma razão que você não fez ![viewControllers containsObject:self], em vez de [viewControllers indexOfObject:self] == NSNotFound? Escolha de estilo?
zekel
24
Esta resposta está obsoleta desde o iOS 5. O -isMovingFromParentViewControllermétodo mencionado abaixo permite testar se a exibição está sendo exibida explicitamente.
Grahamparks # 1/13
136

Eu acho que a maneira mais fácil é:

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

Rápido:

override func viewWillDisappear(animated: Bool)
{
    if isMovingFromParent
    {
        print("View controller was popped")
    }
    else
    {
        print("New view controller was pushed")
    }
    super.viewWillDisappear(animated)
}
RTasche
fonte
No iOS 5, esta é a resposta, talvez também verifique isBeingDismissed
d370urn3ur
4
Para o iOS7, eu tenho que verificar [self.navigationController.viewControllers indexOfObject: self] == NSNotFound novamente porque o aplicativo em segundo plano também passa no teste, mas não se remove da pilha de navegação.
Eric Chen
3
Apple forneceu uma maneira documentada para fazer isso - stackoverflow.com/a/33478133/385708
Shyam Bhat
O problema com o uso do viewWillDisappear é que é possível que o controlador seja retirado da pilha enquanto a exibição já estiver desaparecida. Por exemplo, outro viewcontroller pode ser colocado no topo da pilha e chamar popToRootViewControllerAnimated ignorando viewWillDisappear nos que estão no meio.
John K
Suponha que você tenha dois controladores (vc raiz e outro enviado) em sua pilha de navegação. Quando o terceiro está sendo pressionado, viewWillDisappear é chamado no segundo cuja visualização desaparecerá, certo? Então, quando você passa para o controlador de exibição raiz (exibe o terceiro e o segundo), o viewWillDisappear é chamado no terceiro, ou seja, o último vc na pilha, porque está no topo e desaparece nesse momento, e o segundo já desapareceu. É por isso que esse método é chamado viewWillDisappear e não viewControllerWillBePopped.
RTasche
61

Da documentação da Apple em UIViewController.h:

"Esses quatro métodos podem ser usados ​​nos retornos de chamada de aparência de um controlador de exibição para determinar se ele está sendo apresentado, dispensado ou adicionado ou removido como um controlador de exibição filho. Por exemplo, um controlador de exibição pode verificar se está desaparecendo porque foi descartado ou apareceu perguntando-se em seu método viewWillDisappear:, verificando a expressão ([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);

Então, sim, a única maneira documentada de fazer isso é a seguinte:

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

Versão Swift 3:

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

Se você quer apenas saber se sua visualização está sendo exibida, eu acabei de descobrir que self.navigationControllerestá nildentro viewDidDisappear, quando é removida da pilha de controladores. Portanto, esse é um teste alternativo simples.

(Descobri isso depois de tentar todos os tipos de outras contorções. Estou surpreso que não exista um protocolo de controlador de navegação para registrar um controlador de exibição para ser notificado em pops. Você não pode usá-lo UINavigationControllerDelegateporque isso realmente funciona de verdade.)

dk.
fonte
16

Swift 4

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

Em Swift:

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

    super.viewWillDisappear(animated)

}
user754905
fonte
Certifique-se de usar como! em vez de como
dfmuir
2

Acho que a documentação da Apple sobre isso é difícil de entender. Esta extensão ajuda a ver os estados em cada navegação.

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

Esta pergunta é bastante antiga, mas eu a vi por acidente, por isso quero postar as melhores práticas (afaik)

você pode apenas fazer

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

Isso se aplica ao iOS7 , não faz ideia se se aplica a outros. Pelo que sei, na viewDidDisappearvisão já foi exibida. O que significa que quando você consulta, self.navigationController.viewControllersvocê obtém um nil. Então basta verificar se isso é nulo.

TL; DR

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

Os segmentos podem ser uma maneira muito eficaz de lidar com esse problema no iOS 6+. Se você forneceu um identificador a um determinado seguidor no Interface Builder, é possível fazer o check-in 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
fonte
1

Obrigado @Bryan Henry, ainda trabalha em 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
fonte
-1

Suponho que você queira dizer que sua visualização está sendo movida para baixo na pilha do controlador de navegação, pressionando uma nova visualização quando você diz empurrado para a pilha. Sugiro usar o viewDidUnloadmétodo para adicionar uma NSLogdeclaração para escrever algo para o console para que você possa ver o que está acontecendo, você pode querer adicionar um NSLogpara viewWillDissappeer.

Aaron
fonte
-1

Aqui está uma categoria para realizar o mesmo que a resposta do sbrocket:

Cabeçalho:

#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
bbrame
fonte