web-dev-qa-db-fra.com

rappel du bouton de retour dans navigationController sous iOS

J'ai poussé une vue sur le contrôleur de navigation et lorsque j'appuie sur le bouton précédent, elle passe automatiquement à la vue précédente. Je veux faire quelques choses lorsque le bouton Précédent est enfoncé avant de sortir la vue de la pile. Quelle est la fonction de rappel du bouton de retour?

96
Namratha

William Jockusch's répondre résoudre ce problème avec astuce.

-(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];
}
159
ymutlu

A mon avis la meilleure solution.

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

Mais cela ne fonctionne qu'avec iOS5 +

82
Blank

il est probablement préférable de remplacer le bouton précédent pour pouvoir gérer l'événement avant l'affichage est affiché pour la confirmation de l'utilisateur, par exemple.

dans viewDidLoad créer un UIBarButtonItem et définir self.navigationItem.leftBarButtonItem pour qu'il passe dans un sel

- (void) viewDidLoad
{
// change the back button to cancel and add an event handler
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@”back”
style:UIBarButtonItemStyleBordered
target:self
action:@selector(handleBack:)];

self.navigationItem.leftBarButtonItem = backButton;
[backButton release];

}
- (void) handleBack:(id)sender
{
// pop to root view controller
[self.navigationController popToRootViewControllerAnimated:YES];

}

Ensuite, vous pouvez faire des choses comme soulever une UIAlertView pour confirmer l’action, puis afficher le contrôleur de vue, etc.

Ou bien, au lieu de créer un nouveau bouton arrière, vous pouvez vous conformer aux méthodes de délégation UINavigationController pour effectuer des actions lorsque le bouton Précédent est enfoncé.

25
roocell

Je me retrouve avec ces solutions. Comme nous tapons sur le bouton retour méthode viewDidDisappear appelée. nous pouvons vérifier en appelant le sélecteur isMovingFromParentViewController qui renvoie true. nous pouvons transmettre des données (à l'aide de Delegate). espérons que cela aidera quelqu'un.

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

    }
    if (self.isMovingFromParentViewController) {
       //moving back
        //pass to viewCollection delegate and update UI
        [self.delegateObject passBackSavedData:self.dataModel];

    }
}
8
Avijit Nagare

Pour "AVANT de supprimer la vue de la pile":

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        NSLog(@"do whatever you want here");
    }
}
7
Anum Malik

C'est la bonne façon de détecter cela. 

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        //do stuff

    }
}

cette méthode est appelée lorsque la vue est également poussée. Donc, vérifier le parent == nil est pour sauter le contrôleur de vue de la pile

6
Saad

Il existe un moyen plus approprié que de demander à viewControllers. Vous pouvez faire de votre contrôleur un délégué de la barre de navigation contenant le bouton Précédent. Voici un exemple. Dans l'implémentation du contrôleur sur lequel vous souhaitez gérer l'appui du bouton Précédent, indiquez-lui qu'il implémentera le protocole UINavigationBarDelegate:

@interface MyViewController () <UINavigationBarDelegate>

Puis quelque part dans votre code d’initialisation (probablement dans viewDidLoad), attribuez à votre contrôleur le délégué de sa barre de navigation:

self.navigationController.navigationBar.delegate = self;

Enfin, implémentez la méthode shouldPopItem. Cette méthode est appelée dès que vous appuyez sur le bouton Précédent. Si vous avez plusieurs contrôleurs ou éléments de navigation dans la pile, vous voudrez probablement vérifier lequel de ces éléments de navigation est affiché (paramètre d'élément), afin que vous ne personnalisiez que ce que vous souhaitez. Voici un exemple:

-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    NSLog(@"Back button got pressed!");
    //if you return NO, the back button press is cancelled
    return YES;
}
4
Carlos Guzman

Voici une autre méthode que j'ai implémentée (je ne l'ai pas testée avec une séquence de décompression mais elle ne ferait probablement pas de différence, comme d'autres l'ont indiqué concernant d'autres solutions sur cette page) pour que le contrôleur de vue parent effectue des actions avant l'enfant VC cela poussé est sorti de la pile de vues (je l'ai utilisé quelques niveaux plus bas que le UINavigationController d'origine). Cela pourrait également être utilisé pour effectuer des actions avant que l'enfantVC ne soit poussé. Cela présente l’avantage supplémentaire de travailler avec le bouton de retour du système iOS, au lieu de créer un UIBarButtonItem ou un UIButton personnalisé.

  1. Demandez à votre parent VC d’adopter le protocole UINavigationControllerDelegate et de s’enregistrer pour les messages de délégué:

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
    
  2. Implémentez cette méthode d'instance UINavigationControllerDelegate dans MyParentViewController:

    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
        // Test if operation is a pop; can also test for a Push (i.e., do something before the ChildVC is pushed
        if (operation == UINavigationControllerOperationPop) {
            // Make sure it's the child class you're looking for
            if ([fromVC isKindOfClass:[ChildViewController class]]) {
                // Can handle logic here or send to another method; can also access all properties of child VC at this time
                return [self didPressBackButtonOnChildViewControllerVC:fromVC];
            }
        }
        // If you don't want to specify a nav controller transition
        return nil;
    }
    
  3. Si vous spécifiez une fonction de rappel spécifique dans la méthode d'instance UINavigationControllerDelegate ci-dessus

    -(id <UIViewControllerAnimatedTransitioning>)didPressBackButtonOnAddSearchRegionsVC:(UIViewController *)fromVC {
        ChildViewController *childVC = ChildViewController.new;
        childVC = (ChildViewController *)fromVC;
    
        // childVC.propertiesIWantToAccess go here
    
        // If you don't want to specify a nav controller transition
        return nil;
    

    }

3
Evan R

Si vous ne pouvez pas utiliser "viewWillDisappear" ou une méthode similaire, essayez de sous-classer UINavigationController. C'est la classe d'en-tête:

#import <Foundation/Foundation.h>
@class MyViewController;

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

Classe d'implémentation:

#import "CCNavigationController.h"
#import "MyViewController.h"

@implementation CCNavigationController {

}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
    @"This is the moment for you to do whatever you want"
    [self.viewController doCustomMethod];
    return [super popViewControllerAnimated:animated];
}

@end

D'autre part, vous devez lier ce viewController à votre NavigationController personnalisé. Ainsi, dans votre méthode viewDidLoad pour votre viewController normal, procédez comme suit:

@implementation MyViewController {
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        ((CCNavigationController*)self.navigationController).viewController = self;
    }
}
3
George Harley

C'est peut-être un peu trop tard, mais je voulais aussi le même comportement avant. Et la solution que j'ai choisie fonctionne assez bien avec l'une des applications actuellement sur l'App Store. Comme je n'ai vu personne adopter une méthode similaire, j'aimerais la partager ici. L'inconvénient de cette solution est qu'elle nécessite un sous-classement UINavigationController. Bien que l'utilisation de Method Swizzling puisse aider à l'éviter, je ne suis pas allé aussi loin.

Le bouton Précédent par défaut est donc géré par UINavigationBar. Lorsqu'un utilisateur appuie sur le bouton Précédent, UINavigationBar demande à son délégué s'il doit afficher la UINavigationItem supérieure en appelant navigationBar(_:shouldPop:). UINavigationController implémente réellement ceci, mais il ne déclare pas publiquement qu'il adopte UINavigationBarDelegate (pourquoi !?). Pour intercepter cet événement, créez une sous-classe de UINavigationController, déclarez sa conformité à UINavigationBarDelegate et implémentez navigationBar(_:shouldPop:). Retourne true si l'élément du haut doit être sauté. Retourner false s'il doit rester.

Il y a deux problèmes. La première est que vous devez appeler la version UINavigationController de navigationBar(_:shouldPop:) à un moment donné. Mais UINavigationBarController ne déclare pas publiquement sa conformité à UINavigationBarDelegate; essayer de l'appeler entraînerait une erreur lors de la compilation. La solution que j'ai choisie consiste à utiliser le runtime d'Objective-C pour obtenir directement l'implémentation et l'appeler. S'il vous plaît laissez-moi savoir si quelqu'un a une meilleure solution.

L’autre problème est que navigationBar(_:shouldPop:) est appelé en premier suivi de popViewController(animated:) si l’utilisateur appuie sur le bouton Précédent. L'ordre est inversé si le contrôleur de vue est affiché en appelant popViewController(animated:). Dans ce cas, j'utilise un booléen pour détecter si popViewController(animated:) est appelé avant navigationBar(_:shouldPop:), ce qui signifie que l'utilisateur a tapé sur le bouton Précédent.

De plus, je fais une extension de UIViewController pour permettre au contrôleur de navigation de demander au contrôleur de vue s'il doit être affiché si l'utilisateur appuie sur le bouton Précédent. Les contrôleurs de vue peuvent renvoyer false et effectuer les actions nécessaires, puis appeler popViewController(animated:) ultérieurement.

class InterceptableNavigationController: UINavigationController, UINavigationBarDelegate {
    // If a view controller is popped by tapping on the back button, `navigationBar(_:, shouldPop:)` is called first follows by `popViewController(animated:)`.
    // If it is popped by calling to `popViewController(animated:)`, the order reverses and we need this flag to check that.
    private var didCallPopViewController = false

    override func popViewController(animated: Bool) -> UIViewController? {
        didCallPopViewController = true
        return super.popViewController(animated: animated)
    }

    func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // If this is a subsequence call after `popViewController(animated:)`, we should just pop the view controller right away.
        if didCallPopViewController {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        }

        // The following code is called only when the user taps on the back button.

        guard let vc = topViewController, item == vc.navigationItem else {
            return false
        }

        if vc.shouldBePopped(self) {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        } else {
            return false
        }
    }

    func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
        didCallPopViewController = false
    }

    /// Since `UINavigationController` doesn't publicly declare its conformance to `UINavigationBarDelegate`,
    /// trying to called `navigationBar(_:shouldPop:)` will result in a compile error.
    /// So, we'll have to use Objective-C runtime to directly get super's implementation of `navigationBar(_:shouldPop:)` and call it.
    private func originalImplementationOfNavigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        let sel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:))
        let imp = class_getMethodImplementation(class_getSuperclass(InterceptableNavigationController.self), sel)
        typealias ShouldPopFunction = @convention(c) (AnyObject, Selector, UINavigationBar, UINavigationItem) -> Bool
        let shouldPop = unsafeBitCast(imp, to: ShouldPopFunction.self)
        return shouldPop(self, sel, navigationBar, item)
    }
}

extension UIViewController {
    @objc func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        return true
    }
}

Et dans la vue des contrôleurs, implémentez shouldBePopped(_:). Si vous n'implémentez pas cette méthode, le comportement par défaut consiste à afficher le contrôleur de vue dès que l'utilisateur appuie sur le bouton Précédent, comme d'habitude.

class MyViewController: UIViewController {
    override func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        let alert = UIAlertController(title: "Do you want to go back?",
                                      message: "Do you really want to go back? Tap on \"Yes\" to go back. Tap on \"No\" to stay on this screen.",
                                      preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { _ in
            navigationController.popViewController(animated: true)
        }))
        present(alert, animated: true, completion: nil)
        return false
    }
}

Vous pouvez regarder ma démo ici .

 enter image description here 

2
yusuke024

Voici ce que cela fonctionne pour moi dans Swift:

override func viewWillDisappear(_ animated: Bool) {
    if self.navigationController?.viewControllers.index(of: self) == nil {
        // back button pressed or back gesture performed
    }

    super.viewWillDisappear(animated)
}
1
pableiros

Si vous utilisez un Storyboard et que vous venez d'une transition Push, vous pouvez également remplacer le shouldPerformSegueWithIdentifier:sender:.

0
Mojo66