viewWillDisappear: determine si el controlador de vista se está desplegando o si muestra un controlador de subvista

134

Estoy luchando por encontrar una buena solución a este problema. En el -viewWillDisappear:método de un controlador de vista , necesito encontrar una manera de determinar si se debe a que un controlador de vista está siendo empujado a la pila del controlador de navegación, o si es porque el controlador de vista está desapareciendo porque se ha abierto.

En este momento estoy configurando banderas como, isShowingChildViewControllerpero se está volviendo bastante complicado. La única forma en que creo que puedo detectarlo es en el -deallocmétodo.

Cascada de michael
fuente

Respuestas:

228

Puedes usar lo siguiente.

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

Esto, por supuesto, es posible porque la pila del controlador de vista UINavigationController (expuesta a través de la propiedad viewControllers) se ha actualizado cuando se llama a viewWillDisappear.

Bryan Henry
fuente
2
¡Perfecto! ¡No sé por qué no pensé en eso! ¡Supongo que no pensé que la pila se alteraría hasta que se llamaran los métodos de desaparecer! Gracias :-)
Michael Waterfall
1
Solo he estado tratando de hacer lo mismo, pero viewWillAppearparece que si el controlador de vista se revela al presionarlo o si se abre algo por encima, ¡la matriz viewControllers es la misma en ambos sentidos! ¿Algunas ideas?
Michael Waterfall, el
También debo tener en cuenta que el controlador de vista es persistente durante la vida útil de la aplicación, por lo que no puedo realizar mis acciones viewDidLoadya que solo se llama una vez. ¡Mmm, complicado!
Michael Waterfall el
44
@Sbrocket hay una razón que no hiciste ![viewControllers containsObject:self]en lugar de [viewControllers indexOfObject:self] == NSNotFound? Elección de estilo?
zekel
24
Esta respuesta ha quedado obsoleta desde iOS 5. El -isMovingFromParentViewControllermétodo mencionado a continuación le permite probar si la vista se muestra explícitamente.
Grahamparks
136

Creo que la forma más fácil es:

 - (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
fuente
A partir de iOS 5 esta es la respuesta, quizás también verifique isBeingDismissed
d370urn3ur
44
Para iOS7 tengo que verificar [self.navigationController.viewControllers indexOfObject: self] == NSNotFound nuevamente porque la aplicación en segundo plano también pasará esta prueba pero no eliminará self de la pila de navegación.
Eric Chen
3
Apple ha proporcionado una forma documentada de hacer esto - stackoverflow.com/a/33478133/385708
Shyam Bhat
El problema con el uso de viewWillDisappear es que es posible que el controlador salga de la pila mientras la vista ya desapareció. Por ejemplo, otro controlador de vista podría empujarse en la parte superior de la pila y luego llamar a popToRootViewControllerAnimated omitiendo viewWillDisappear en los del medio.
John K
Supongamos que tiene dos controladores (root vc y otro empujado) en su pila de navegación. Cuando se empuja el tercero viewWillDisappear se llama al segundo cuya vista va a desaparecer, ¿verdad? Entonces, cuando aparece en el controlador de vista raíz (aparece el tercero y el segundo), viewWillDisappear se llama en el tercero, es decir, el último vc en la pila porque su vista está en la parte superior y va a desaparecer en este momento y la vista del segundo ya había desaparecido. Es por eso que este método se llama viewWillDisappear y no viewControllerWillBePopped.
RTasche
61

De la documentación de Apple en UIViewController.h:

"Estos cuatro métodos se pueden usar en las devoluciones de llamada de apariencia de un controlador de vista para determinar si se está presentando, descartando o agregando o eliminando como un controlador de vista secundario. Por ejemplo, un controlador de vista puede verificar si está desapareciendo porque se descartó o apareció preguntándose en su método viewWillDisappear: comprobando la expresión ([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);

Entonces, sí, la única forma documentada de hacer esto es la siguiente:

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

Versión Swift 3:

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

Si solo quiere saber si su vista está apareciendo, acabo de descubrir que self.navigationControllerestá nildentro viewDidDisappear, cuando se elimina de la pila de controladores. Entonces esa es una prueba alternativa simple.

(Esto lo descubro después de probar todo tipo de otras contorsiones. Me sorprende que no haya un protocolo de controlador de navegación para registrar un controlador de vista para que se le notifique en los pops. No se puede usar UINavigationControllerDelegateporque realmente hace un trabajo de visualización real).

dk.
fuente
16

Swift 4

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

En Swift:

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

    super.viewWillDisappear(animated)

}
usuario754905
fuente
¡Asegúrate de usar como! en lugar de como
dfmuir
2

Encuentro que la documentación de Apple sobre esto es difícil de entender. Esta extensión ayuda a ver los estados en cada navegación.

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

Esta pregunta es bastante antigua, pero la vi por accidente, así que quiero publicar las mejores prácticas (afaik)

solo puedes hacer

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

Esto se aplica a iOS7 , no tengo idea si se aplica a otros. Por lo que sé, en viewDidDisappearla vista ya se ha reventado. Lo que significa que cuando consulta self.navigationController.viewControllersobtendrá un nil. Así que solo verifica si eso es nulo.

TL; DR

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

Los segmentos pueden ser una forma muy efectiva de manejar este problema en iOS 6+. Si le ha dado al segmento particular un identificador en Interface Builder, puede 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
    }
}
Kyle Clegg
fuente
1

Gracias @Bryan Henry, todavía trabaja en 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
fuente
-1

Supongo que quiere decir que su vista se está moviendo hacia abajo en la pila del controlador de navegación al presionar una nueva vista cuando dice empujado a la pila. Sugeriría usar el viewDidUnloadmétodo para agregar una NSLogdeclaración para escribir algo en la consola para que pueda ver lo que está sucediendo, es posible que desee agregar un NSLoga viewWillDissappeer.

Aaron
fuente
-1

Aquí hay una categoría para lograr lo mismo que la respuesta de sbrocket:

Encabezamiento:

#import <UIKit/UIKit.h>

@interface UIViewController (isBeingPopped)

- (BOOL) isBeingPopped;

@end

Fuente:

#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
fuente