Action de réglage pour le bouton retour dans le contrôleur de navigation

J’essaie d’écraser l’action par défaut du bouton Retour dans un contrôleur de navigation. J’ai fourni à une cible une action sur le bouton personnalisé. Ce qui est étrange, c’est qu’en l’atsortingbuant, l’atsortingbut backbutton ne leur prête pas attention et il affiche simplement la vue courante et retourne à la racine:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle: @"Servers" style:UIBarButtonItemStylePlain target:self action:@selector(home)]; self.navigationItem.backBarButtonItem = backButton; 

Dès que je l’ai défini via le leftBarButtonItem sur le navigationItem, il appelle mon action, cependant le bouton ressemble à un simple rond au lieu de l’arrière fléché:

 self.navigationItem.leftBarButtonItem = backButton; 

Comment puis-je le faire appeler mon action personnalisée avant de retourner à la vue racine? Existe-t-il un moyen de remplacer l’action par défaut, ou existe-t-il une méthode toujours appelée lorsqu’on quitte une vue (viewDidUnload ne le fait pas)?

Essayez de mettre ceci dans le contrôleur de vue où vous voulez détecter la presse:

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

J’ai implémenté l’extension UIViewController-BackButtonHandler . Il n’est pas nécessaire de sous- UIViewController quoi que ce soit, placez-le simplement dans votre projet et remplacez navigationShouldPopOnBackButton méthode navigationShouldPopOnBackButton dans la classe UIViewController :

 -(BOOL) navigationShouldPopOnBackButton { if(needsShowConfirmation) { // Show confirmation alert // ... return NO; // Ignore 'Back' button this time } return YES; // Process 'Back' button click and Pop view controler } 

Télécharger un exemple d’application

Contrairement à Amagrammer, c’est possible. Vous devez sous-classer votre navigateur de navigation. J’ai tout expliqué ici (y compris un exemple de code): http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller

Version rapide:

(de https://stackoverflow.com/a/19132881/826435 )

Dans votre contrôleur de vue, vous vous conformez simplement à un protocole et effectuez les actions dont vous avez besoin:

 extension MyViewController: NavigationControllerBackButtonDelegate { func shouldPopOnBackButtonPress() -> Bool { performSomeActionOnThePressOfABackButton() return false } } 

Ensuite, créez une classe, dites NavigationController+BackButton , et copiez-collez simplement le code ci-dessous:

 protocol NavigationControllerBackButtonDelegate { func shouldPopOnBackButtonPress() -> Bool } extension UINavigationController { public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { // Prevents from a synchronization issue of popping too many navigation items // and not enough view controllers or viceversa from unusual tapping if viewControllers.count < navigationBar.items!.count { return true } // Check if we have a view controller that wants to respond to being popped var shouldPop = true if let viewController = topViewController as? NavigationControllerBackButtonDelegate { shouldPop = viewController.shouldPopOnBackButtonPress() } if (shouldPop) { DispatchQueue.main.async { self.popViewController(animated: true) } } else { // Prevent the back button from staying in an disabled state for view in navigationBar.subviews { if view.alpha < 1.0 { UIView.animate(withDuration: 0.25, animations: { view.alpha = 1.0 }) } } } return false } } 

Il n’est pas possible de faire directement. Il y a quelques alternatives:

  1. Créez votre propre UIBarButtonItem personnalisé qui valide à UIBarButtonItem si le test réussit
  2. Validez le contenu du champ de formulaire à l’aide d’une méthode de délégué UITextField , telle que -textFieldShouldReturn: appelée après avoir appuyé sur le bouton Return ou Done du clavier

L’inconvénient de la première option est que le bouton fléché vers la gauche du bouton arrière n’est pas accessible à partir d’un bouton de barre personnalisé. Donc, vous devez utiliser une image ou aller avec un bouton de style régulier.

La deuxième option est intéressante car vous récupérez le champ de texte dans la méthode de délégation, vous pouvez donc cibler votre logique de validation sur le champ de texte spécifique envoyé à la méthode de rappel des delegates.

Pour des raisons de thread, la solution mentionnée par @HansPinckaers ne me convenait pas, mais j’ai trouvé un moyen très simple d’attraper une touche sur le bouton de retour, et je veux vous le dire ici pour éviter des heures de déception pour quelqu’un d’autre. L’astuce est vraiment simple: ajoutez un UIButton transparent en tant que sous-vue à votre UINavigationBar, et définissez ses sélecteurs comme s’il s’agissait du véritable bouton! Voici un exemple d’utilisation de Monotouch et C #, mais la traduction vers objective-c ne devrait pas être trop difficile à trouver.

 public class Test : UIViewController { public override void ViewDidLoad() { UIButton b = new UIButton(new RectangleF(0, 0, 60, 44)); //width must be adapted to label contained in button b.BackgroundColor = UIColor.Clear; //making the background invisible b.Title = ssortingng.Empty; // and no need to write anything b.TouchDown += delegate { Console.WriteLine("caught!"); if (true) // check what you want here NavigationController.PopViewControllerAnimated(true); // and then we pop if we want }; NavigationController.NavigationBar.AddSubview(button); // insert the button to the nav bar } } 

Fait amusant: à des fins de test et pour trouver de bonnes dimensions pour mon faux bouton, je règle sa couleur d’arrière-plan au bleu … Et ça se voit derrière le bouton de retour! Quoi qu’il en soit, il touche toujours le bouton d’origine.

Cette technique vous permet de modifier le texte du bouton “Retour” sans affecter le titre d’aucun des contrôleurs de vue ou de voir le changement du texte du bouton pendant l’animation.

Ajoutez ceci à la méthode init dans le contrôleur de la vue appelante :

 UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init]; temporaryBarButtonItem.title = @"Back"; self.navigationItem.backBarButtonItem = temporaryBarButtonItem; [temporaryBarButtonItem release]; 

Manière la plus simple

Vous pouvez utiliser les méthodes de délégué UINavigationController. La méthode willShowViewController est appelée lorsque vous appuyez sur le bouton arrière de votre VC.

 - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated; 

Nous avons trouvé une solution qui conserve le style de bouton arrière également. Ajoutez la méthode suivante à votre contrôleur de vue.

 -(void) overrideBack{ UIButton *transparentButton = [[UIButton alloc] init]; [transparentButton setFrame:CGRectMake(0,0, 50, 40)]; [transparentButton setBackgroundColor:[UIColor clearColor]]; [transparentButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside]; [self.navigationController.navigationBar addSubview:transparentButton]; } 

Fournissez maintenant une fonctionnalité nécessaire dans la méthode suivante:

 -(void)backAction:(UIBarButtonItem *)sender { //Your functionality } 

Il suffit de couvrir le bouton arrière avec un bouton transparent;)

Je ne crois pas que ce soit possible, facilement. La seule façon de contourner ce problème est de placer votre propre image en forme de bouton de retour. C’était frustrant pour moi au début, mais je vois pourquoi, pour des raisons de cohérence, il a été laissé de côté.

Vous pouvez vous rapprocher (sans la flèche) en créant un bouton normal et en masquant le bouton de retour par défaut:

 self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease]; self.navigationItem.hidesBackButton = YES; 

Il existe un moyen plus simple de sous- UINavigationBar simplement la méthode delegate de UINavigationBar et de remplacer la méthode ShouldPopItem .

Voici ma solution Swift. Dans votre sous-classe de UIViewController, remplacez la méthode navigationShouldPopOnBackButton.

 extension UIViewController { func navigationShouldPopOnBackButton() -> Bool { return true } } extension UINavigationController { func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool { if let vc = self.topViewController { if vc.navigationShouldPopOnBackButton() { self.popViewControllerAnimated(true) } else { for it in navigationBar.subviews { let view = it as! UIView if view.alpha < 1.0 { [UIView .animateWithDuration(0.25, animations: { () -> Void in view.alpha = 1.0 })] } } return false } } return true } } 

La solution onegray n’est pas sûre. Selon les documents officiels d’Apple, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html , nous devrions éviter de le faire.

“Si le nom d’une méthode déclarée dans une catégorie est le même que celui d’une méthode de la classe d’origine ou d’une méthode d’une autre catégorie de la même classe (ou même d’une super-classe), le comportement n’est pas défini Cela est moins susceptible de poser problème si vous utilisez des catégories avec vos propres classes, mais cela peut poser problème lorsque vous utilisez des catégories pour append des méthodes aux classes Cocoa ou Cocoa Touch standard. ”

En utilisant Swift:

 override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if self.navigationController?.topViewController != self { print("back button tapped") } } 

Voici la version 3 de Swift 3 de la réponse de @oneway pour intercepter l’événement du bouton de retour de la barre de navigation avant qu’il ne soit déclenché. Comme UINavigationBarDelegate ne peut pas être utilisé pour UIViewController , vous devez créer un délégué qui sera déclenché lorsque navigationBar shouldPop est appelée.

 @objc public protocol BackButtonDelegate { @objc optional func navigationShouldPopOnBackButton() -> Bool } extension UINavigationController: UINavigationBarDelegate { public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { if viewControllers.count < (navigationBar.items?.count)! { return true } var shouldPop = true let vc = self.topViewController if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) { shouldPop = vc.navigationShouldPopOnBackButton() } if shouldPop { DispatchQueue.main.async { self.popViewController(animated: true) } } else { for subView in navigationBar.subviews { if(0 < subView.alpha && subView.alpha < 1) { UIView.animate(withDuration: 0.25, animations: { subView.alpha = 1 }) } } } return false } } 

Et puis, dans votre vue, le contrôleur ajoute la fonction de délégué:

 class BaseVC: UIViewController, BackButtonDelegate { func navigationShouldPopOnBackButton() -> Bool { if ... { return true } else { return false } } } 

Je me suis rendu compte que nous voulons souvent append un contrôleur d’alerte pour que les utilisateurs puissent décider s’ils veulent revenir en arrière. Si c'est le cas, vous pouvez toujours return false dans la fonction navigationShouldPopOnBackButton() et fermer votre contrôleur de vue en procédant comme suit:

 func navigationShouldPopOnBackButton() -> Bool { let alert = UIAlertController(title: "Warning", message: "Do you want to quit?", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()})) alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()})) present(alert, animated: true, completion: nil) return false } func yes() { print("yes") DispatchQueue.main.async { _ = self.navigationController?.popViewController(animated: true) } } func no() { print("no") } 

En utilisant les variables cible et action que vous quittez actuellement «nil», vous devriez être en mesure de connecter vos boîtes de dialog de sauvegarde pour qu’elles soient appelées lorsque le bouton est «sélectionné». Attention, cela peut être déclenché à des moments étranges.

Je suis principalement d’accord avec Amagrammer, mais je ne pense pas que ce serait si difficile de faire le bouton avec la flèche personnalisée. Je voudrais juste renommer le bouton de retour, prendre une capture d’écran, la taille de bouton nécessaire, et avoir l’image sur le dessus de votre bouton.

Vous pouvez essayer d’accéder à l’élément Bouton droit de navigationBars et définir sa propriété sélecteur … comme référence UIBarButtonItem , une autre chose si ce dernier travail qui fonctionnera est de définir l’élément bouton droit de la barre de navigation sur un UIBarButtonItem personnalisé. créer et définir son sélecteur … espère que cela aide

Pour un formulaire qui nécessite une saisie utilisateur comme celle-ci, je vous recommande de l’invoquer comme un “modal” au lieu d’une partie de votre stack de navigation. De cette façon, ils doivent s’occuper des affaires sur le formulaire, puis vous pouvez le valider et le supprimer en utilisant un bouton personnalisé. Vous pouvez même concevoir une barre de navigation qui ressemble au rest de votre application, mais vous donne plus de contrôle.

Au moins dans Xcode 5, il existe une solution simple et plutôt bonne (pas parfaite). Dans IB, faites glisser un élément de bouton de barre du volet Utilitaires et déposez-le sur le côté gauche de la barre de navigation où se trouve le bouton Précédent. Définissez l’étiquette sur “Retour”. Vous aurez un bouton fonctionnel que vous pourrez lier à votre IBAction et fermer votre viewController. Je fais du travail puis déclenche une phase de déroulement et cela fonctionne parfaitement.

Ce qui n’est pas idéal, c’est que ce bouton n’a pas la flèche

Vous vous retrouvez également avec deux boutons Retour dans le navigateur IB, mais il est assez facile de les étiqueter pour plus de clarté.

entrer la description de l'image ici

Rapide

 override func viewWillDisappear(animated: Bool) { let viewControllers = self.navigationController?.viewControllers! if indexOfArray(viewControllers!, searchObject: self) == nil { // do something } super.viewWillDisappear(animated) } func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? { for (index, value) in enumerate(array) { if value as UIViewController == searchObject as UIViewController { return index } } return nil } 

Cette approche a fonctionné pour moi (mais le bouton “Retour” n’aura pas le signe “<"):

 - (void)viewDidLoad { [super viewDidLoad]; UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStyleBordered target:self action:@selector(backButtonClicked)]; self.navigationItem.leftBarButtonItem = backNavButton; } -(void)backButtonClicked { // Do something... AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate]; [delegate.navController popViewControllerAnimated:YES]; } 

La réponse de @William est correcte, cependant, si l’utilisateur lance un geste de balayage vers l’arrière, la méthode viewWillDisappear est appelée et même “self” ne figurera pas dans la stack de navigation (c’est-à-dire que self.navigationController.viewControllers a gagné). ne contient pas “self”), même si le balayage n’est pas terminé et que le contrôleur de vue n’est pas réellement sauté. Ainsi, la solution serait de:

  1. Désactivez le geste de glisser-arrière dans viewDidAppear et n’utilisez le bouton Précédent qu’en utilisant:

     if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enabled = NO; } 
  2. Ou utilisez simplement viewDidDisappear comme suit:

     - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; if (![self.navigationController.viewControllers containsObject:self]) { // back button was pressed or the the swipe-to-go-back gesture was // completed. We know this is true because self is no longer // in the navigation stack. } } 

Utilisez isMovingFromParentViewController

 override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(true) if self.isMovingFromParentViewController { // current viewController is removed from parent // do some work } } 

Version Swift 4 iOS 11.3:

Cela s’appuie sur la réponse de kgaidis sur https://stackoverflow.com/a/34343418/4316579

Je ne sais pas quand l’extension a cessé de fonctionner, mais au moment d’écrire ces lignes (Swift 4), il semble que l’extension ne sera plus exécutée à moins que vous ne déclariez la conformité UINavigationBarDelegate comme décrit ci-dessous.

J’espère que cela aidera les gens qui se demandent pourquoi leur extension ne fonctionne plus.

 extension UINavigationController: UINavigationBarDelegate { public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { } } 

Pour intercepter le bouton Retour, il suffit de le couvrir avec un contrôle UIC transparent et d’intercepter les contacts.

 @interface MyViewController : UIViewController { UIControl *backCover; BOOL inhibitBackButtonBOOL; } @end @implementation MyViewController -(void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // Cover the back button (cannot do this in viewWillAppear -- too soon) if ( backCover == nil ) { backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)]; #if TARGET_IPHONE_SIMULATOR // show the cover for testing backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15]; #endif [backCover addTarget:self action:@selector(backCoverAction) forControlEvents:UIControlEventTouchDown]; UINavigationBar *navBar = self.navigationController.navigationBar; [navBar addSubview:backCover]; } } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [backCover removeFromSuperview]; backCover = nil; } - (void)backCoverAction { if ( inhibitBackButtonBOOL ) { NSLog(@"Back button aborted"); // notify the user why... } else { [self.navigationController popViewControllerAnimated:YES]; // "Back" } } @end 

La solution que j’ai trouvée jusqu’à présent n’est pas très agréable, mais elle fonctionne pour moi. En prenant cette réponse , je vérifie également si je saute ou non par programme:

 - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if ((self.isMovingFromParentViewController || self.isBeingDismissed) && !self.isPoppingProgrammatically) { // Do your stuff here } } 

Vous devez append cette propriété à votre contrôleur et la définir sur YES avant de sauter par programme:

 self.isPoppingProgrammatically = YES; [self.navigationController popViewControllerAnimated:YES]; 

Trouvé une nouvelle façon de le faire:

Objectif c

 - (void)didMoveToParentViewController:(UIViewController *)parent{ if (parent == NULL) { NSLog(@"Back Pressed"); } } 

Rapide

 override func didMoveToParentViewController(parent: UIViewController?) { if parent == nil { println("Back Pressed") } } 

Version rapide de la réponse de @ onegray

 protocol RequestsNavigationPopVerification { var confirmationTitle: Ssortingng { get } var confirmationMessage: Ssortingng { get } } extension RequestsNavigationPopVerification where Self: UIViewController { var confirmationTitle: Ssortingng { return "Go back?" } var confirmationMessage: Ssortingng { return "Are you sure?" } } final class NavigationController: UINavigationController { func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool { guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else { popViewControllerAnimated(true) return true } let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert) alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in dispatch_async(dispatch_get_main_queue(), { let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil } UIView.animateWithDuration(0.25) { dimmed.forEach { $0.alpha = 1 } } }) return }) alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in dispatch_async(dispatch_get_main_queue(), { self.popViewControllerAnimated(true) }) }) presentViewController(alertController, animated: true, completion: nil) return false } } 

Maintenant, dans n'importe quel contrôleur, conformez-vous à RequestsNavigationPopVerification et ce comportement est adopté par défaut.