viewWillDisappear: détermine si le contrôleur de vue est en cours de saut ou s'il affiche un contrôleur de sous-vue

134

J'ai du mal à trouver une bonne solution à ce problème. Dans la -viewWillDisappear:méthode d' un contrôleur de vue , je dois trouver un moyen de déterminer si c'est parce qu'un contrôleur de vue est poussé sur la pile du contrôleur de navigation, ou si c'est parce que le contrôleur de vue disparaît parce qu'il a été sauté.

En ce moment, je mets des drapeaux comme isShowingChildViewControllermais ça devient assez compliqué. La seule façon dont je pense que je peux le détecter est dans la -deallocméthode.

Michael Waterfall
la source

Réponses:

228

Vous pouvez utiliser ce qui suit.

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

Ceci est bien sûr possible car la pile de contrôleurs de vue de UINavigationController (exposée via la propriété viewControllers) a été mise à jour au moment où viewWillDisappear est appelé.

Bryan Henry
la source
2
Parfait! Je ne sais pas pourquoi je n'y ai pas pensé! Je suppose que je ne pensais pas que la pile serait modifiée jusqu'à ce que les méthodes de disparition aient été appelées! Merci :-)
Michael Waterfall
1
J'ai juste essayé d'effectuer la même chose, mais viewWillAppearil semblerait que, que le contrôleur de vue soit révélé en poussant ou en sautant quelque chose au-dessus, le tableau viewControllers est le même dans les deux sens! Des idées?
Michael Waterfall
Je dois également noter que le contrôleur de vue est persistant pendant toute la durée de vie de l'application, je ne peux donc pas effectuer mes actions viewDidLoadcar il n'est appelé qu'une seule fois! Hmm, délicat!
Michael Waterfall
4
@Sbrocket y a-t-il une raison pour laquelle vous ne l'avez pas fait à la ![viewControllers containsObject:self]place [viewControllers indexOfObject:self] == NSNotFound? Choix de style?
zekel
24
Cette réponse est obsolète depuis iOS 5. La -isMovingFromParentViewControllerméthode mentionnée ci-dessous vous permet de tester si la vue est affichée explicitement.
grahamparks
136

Je pense que le moyen le plus simple est:

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

Rapide:

override func viewWillDisappear(animated: Bool)
{
    if isMovingFromParent
    {
        print("View controller was popped")
    }
    else
    {
        print("New view controller was pushed")
    }
    super.viewWillDisappear(animated)
}
RTasche
la source
À partir d'iOS 5, c'est la réponse, peut-être aussi vérifier isBeingDismissed
d370urn3ur
4
Pour iOS7, je dois vérifier à nouveau [self.navigationController.viewControllers indexOfObject: self] == NSNotFound car la mise en arrière-plan de l'application passera également ce test mais ne supprimera pas l'auto de la pile de navigation.
Eric Chen
3
Apple a fourni un moyen documenté de le faire - stackoverflow.com/a/33478133/385708
Shyam Bhat
Le problème avec l'utilisation de viewWillDisappear est qu'il est possible que le contrôleur soit sorti de la pile alors que la vue a déjà disparu. Par exemple, un autre viewcontroller pourrait être poussé au-dessus de la pile, puis appeler popToRootViewControllerAnimated en contournant viewWillDisappear sur ceux du milieu.
John K
Supposons que vous ayez deux contrôleurs (root vc et un autre push) sur votre pile de navigation. Lorsque le troisième est poussé, viewWillDisappear est appelé sur le second dont la vue va disparaître, non? Ainsi, lorsque vous passez au contrôleur de vue racine (pop le troisième et le deuxième), viewWillDisappear est appelé sur le troisième, c'est-à-dire le dernier vc de la pile, car sa vue est en haut et va disparaître à ce moment et la vue de la seconde a déjà disparu. C'est pourquoi cette méthode est appelée viewWillDisappear et non viewControllerWillBePopped.
RTasche
61

À partir de la documentation Apple dans UIViewController.h:

"Ces quatre méthodes peuvent être utilisées dans les rappels d'apparence d'un contrôleur de vue pour déterminer s'il est présenté, ignoré, ajouté ou supprimé en tant que contrôleur de vue enfant. Par exemple, un contrôleur de vue peut vérifier s'il disparaît parce qu'il a été ignoré ou surgit en se demandant dans sa méthode viewWillDisappear: en vérifiant l'expression ([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);

Alors oui, la seule façon documentée de le faire est la suivante:

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

Version Swift 3:

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

Si vous voulez juste savoir si votre opinion est s'éclaté, je viens de découvrir que self.navigationControllerest nilen viewDidDisappear, quand il est retiré de la pile de contrôleurs. C'est donc un test alternatif simple.

(Je découvre cela après avoir essayé toutes sortes d'autres contorsions. Je suis surpris qu'il n'y ait pas de protocole de contrôleur de navigation pour enregistrer un contrôleur de vue pour être notifié sur les pops. Vous ne pouvez pas utiliser UINavigationControllerDelegatecar cela fonctionne réellement.)

dk.
la source
16

Swift 4

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

Dans Swift:

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

    super.viewWillDisappear(animated)

}
user754905
la source
Assurez-vous d'utiliser comme! au lieu de as
dfmuir
2

Je trouve que la documentation d'Apple à ce sujet est difficile à comprendre. Cette extension permet de voir les états à chaque navigation.

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

Cette question est assez ancienne mais je l'ai vue par accident donc je veux publier les meilleures pratiques (afaik)

tu peux juste faire

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

Cela s'applique à iOS7 , aucune idée si cela s'applique à d'autres. De ce que je sais, dans viewDidDisappearla vue a déjà été sauté. Ce qui signifie que lorsque vous interrogez, self.navigationController.viewControllersvous obtiendrez un fichier nil. Donc, vérifiez si c'est nul.

TL; DR

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

Les segues peuvent être un moyen très efficace de gérer ce problème dans iOS 6+. Si vous avez donné un identifiant au segment particulier dans Interface Builder, vous pouvez le vérifier 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
la source
1

Merci @Bryan Henry, travaille toujours dans 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
la source
-1

Je suppose que vous voulez dire que votre vue est déplacée vers le bas de la pile du contrôleur de navigation en poussant une nouvelle vue lorsque vous dites poussée sur la pile. Je suggérerais d'utiliser la viewDidUnloadméthode pour ajouter une NSLoginstruction pour écrire quelque chose sur la console afin que vous puissiez voir ce qui se passe, vous pouvez ajouter un NSLogà viewWillDissappeer.

Aaron
la source
-1

Voici une catégorie pour accomplir la même chose que la réponse de sbrocket:

Entête:

#import <UIKit/UIKit.h>

@interface UIViewController (isBeingPopped)

- (BOOL) isBeingPopped;

@end

La source:

#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
la source