149 votes

Détection de l'appui sur le bouton "retour" d'une barre de navigation

J'ai besoin d'exécuter certaines actions lorsque le bouton Retour (retour à l'écran précédent, retour à la vue parent) est pressé sur une barre de navigation.

Existe-t-il une méthode que je puisse mettre en œuvre pour attraper l'événement et déclencher des actions pour mettre en pause et sauvegarder les données avant que l'écran ne disparaisse ?

0 votes

1 votes

Regardez le solution dans ce fil

0 votes

Je l'ai fait de cette façon montrer la décision ici

324voto

elitalon Points 2894

UPDATE : D'après certains commentaires, la solution proposée dans la réponse originale ne semble pas fonctionner dans certains scénarios sous iOS 8+. Je ne peux pas vérifier que c'est effectivement le cas sans plus de détails.

Pour ceux d'entre vous qui se trouvent toutefois dans cette situation, il existe une alternative. La détection de l'ouverture d'un contrôleur de vue est possible en surchargeant la fonction willMove(toParentViewController:) . L'idée de base est qu'un contrôleur de vue est déclenché quand parent est nil .

Vérifiez "Implémentation d'un contrôleur de vue de conteneur" pour plus de détails.


Depuis iOS 5, j'ai constaté que le moyen le plus simple de faire face à cette situation est d'utiliser la nouvelle méthode - (BOOL)isMovingFromParentViewController :

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isMovingFromParentViewController) {
    // Do your stuff here
  }
}

- (BOOL)isMovingFromParentViewController a du sens lorsque vous poussez et retirez des contrôleurs dans une pile de navigation.

Cependant, si vous présentez des contrôleurs de vues modaux, vous devez utiliser - (BOOL)isBeingDismissed à la place :

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isBeingDismissed) {
    // Do your stuff here
  }
}

Comme indiqué dans cette question vous pouvez combiner les deux propriétés :

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isMovingFromParentViewController || self.isBeingDismissed) {
    // Do your stuff here
  }
}

D'autres solutions reposent sur l'existence d'un UINavigationBar . Je préfère mon approche car elle dissocie les tâches à effectuer de l'action qui a déclenché l'événement, c'est-à-dire l'appui sur un bouton retour.

106voto

WCByrne Points 178

Alors que viewWillAppear() et viewDidDisappear() sont sont appelés lorsque l'on appuie sur le bouton retour, ils sont également appelés à d'autres moments. Voir la fin de la réponse pour en savoir plus.

Utilisation de UIViewController.parent

La détection du bouton retour est mieux réalisée lorsque le VC est retiré de son parent (le NavigationController) à l'aide de la fonction willMoveToParentViewController(_:) OU didMoveToParentViewController()

Si parent est nil, le contrôleur de vue est retiré de la pile de navigation et renvoyé. Si parent n'est pas nil, il est ajouté à la pile et présenté.

// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
     [super willMoveToParentViewController:parent];
    if (!parent){
       // The back button was pressed or interactive gesture used
    }
}

// Swift
override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent)
    if parent == nil {
        // The back button was pressed or interactive gesture used
    }
}

Remplacer willMove pour didMove et vérifier le travail de self.parent après le contrôleur de vue est rejeté.

Arrêter le licenciement

Notez que le fait de vérifier le parent ne vous permet pas de "mettre en pause" la transition si vous devez effectuer une sorte de sauvegarde asynchrone. Pour ce faire, vous pouvez implémenter ce qui suit. Le seul inconvénient est que vous perdez le bouton de retour animé de style iOS. Faites également attention au geste de balayage interactif. Utilisez ce qui suit pour gérer ce cas.

var backButton : UIBarButtonItem!

override func viewDidLoad() {
    super.viewDidLoad()

     // Disable the swipe to make sure you get your chance to save
     self.navigationController?.interactivePopGestureRecognizer.enabled = false

     // Replace the default back button
    self.navigationItem.setHidesBackButton(true, animated: false)
    self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
    self.navigationItem.leftBarButtonItem = backButton
}

// Then handle the button selection
func goBack() {
    // Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
    self.navigationItem.leftBarButtonItem = nil
    someData.saveInBackground { (success, error) -> Void in
        if success {
            self.navigationController?.popViewControllerAnimated(true)
            // Don't forget to re-enable the interactive gesture
            self.navigationController?.interactivePopGestureRecognizer.enabled = true
        }
        else {
            self.navigationItem.leftBarButtonItem = self.backButton
            // Handle the error
        }
    }
}

Plus d'informations apparaîtront

Si vous n'avez pas eu le viewWillAppear viewDidDisappear Nous allons voir un exemple. Disons que vous avez trois contrôleurs de vue :

  1. ListVC : Une vue en tableau des choses
  2. DetailVC : Détails sur une chose
  3. ParamètresVC : Quelques options pour une chose

Suivons les appels sur le detailVC à mesure que vous passez de la listVC à settingsVC et de retour à listVC

Liste > Détail (pousser le détailVC) Detail.viewDidAppear <- apparaître
Détail > Paramètres (pousser les paramètresVC) Detail.viewDidDisappear <- disparaître

Et comme nous revenons en arrière...
Paramètres > Détail (pop settingsVC) Detail.viewDidAppear <- apparaître
Détail > Liste (pop detailVC) Detail.viewDidDisappear <- disparaître

Remarquez que viewDidDisappear est appelé plusieurs fois, non seulement en revenant en arrière, mais aussi en allant de l'avant. Pour une opération rapide, cela peut être souhaitable, mais pour une opération plus complexe comme un appel réseau pour sauvegarder, cela peut ne pas être le cas.

21voto

matt Points 60113

Ceux qui prétendent que cela ne fonctionne pas se trompent :

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if self.isMovingFromParent {
        print("we are being popped")
    }
}

Cela fonctionne bien. Alors qu'est-ce qui est à l'origine du mythe répandu selon lequel ce n'est pas le cas ?

Le problème semble être dû à une implémentation incorrecte d'une fonction différents à savoir que la mise en œuvre de willMove(toParent:) a oublié d'appeler super .

Si vous mettez en œuvre willMove(toParent:) sans appeler super alors self.isMovingFromParent sera false et l'utilisation de viewWillDisappear semblera échouer. Il n'a pas échoué, vous l'avez cassé.

NOTE : Le vrai problème est généralement le deuxième contrôleur de vue détectant que le premièrement Le contrôleur de vue a été déclenché. Veuillez également consulter la discussion plus générale ici : Détection unifiée de UIViewController "devenu frontal" ?

EDIT Un commentaire suggère que cela devrait être viewDidDisappear plutôt que viewWillDisappear .

16voto

Nepster Points 893

Première méthode

- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (![parent isEqual:self.parentViewController]) {
         NSLog(@"Back pressed");
    }
}

Deuxième méthode

-(void) viewWillDisappear:(BOOL)animated {
    if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
       // back button was pressed.  We know this is true because self is no longer
       // in the navigation stack.  
    }
    [super viewWillDisappear:animated];
}

1 votes

La deuxième méthode est la seule qui ait fonctionné pour moi. La première méthode était également appelée lorsque ma vue était présentée, ce qui n'était pas acceptable pour mon cas d'utilisation.

9voto

7ynk3r Points 11

Je joue (ou lutte) avec ce problème depuis deux jours. IMO la meilleure approche est juste de créer une classe d'extension et un protocole, comme ceci :

@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
 * Indicates that the back button was pressed.
 * If this message is implemented the pop logic must be manually handled.
 */
- (void)backButtonPressed;
@end

@interface UINavigationController(BackButtonHandler)
@end

@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    UIViewController *topViewController = self.topViewController;
    BOOL wasBackButtonClicked = topViewController.navigationItem == item;
    SEL backButtonPressedSel = @selector(backButtonPressed);
    if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
        [topViewController performSelector:backButtonPressedSel];
        return NO;
    }
    else {
        [self popViewControllerAnimated:YES];
        return YES;
    }
}
@end

Cela fonctionne parce que UINavigationController recevra un appel à navigationBar:shouldPopItem: chaque fois qu'un contrôleur de vue est activé. Là, nous détectons si le bouton "back" a été pressé ou non (tout autre bouton). La seule chose que vous devez faire est d'implémenter le protocole dans le contrôleur de vue où back est pressé.

N'oubliez pas d'insérer manuellement le contrôleur de vue dans backButtonPressedSel si tout va bien.

Si vous avez déjà sous-classé UINavigationViewController et mis en œuvre navigationBar:shouldPopItem: Ne vous inquiétez pas, ça ne va pas interférer avec ça.

Vous pouvez également être intéressé par la désactivation du geste de retour.

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X