Définition de l'action pour le bouton de retour dans le contrôleur de navigation

180

J'essaie de remplacer l'action par défaut du bouton de retour dans un contrôleur de navigation. J'ai fourni une cible une action sur le bouton personnalisé. La chose étrange est que lorsque vous l'assignez via l'attribut backbutton, il ne leur prête pas attention et affiche simplement la vue actuelle 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 le place via le leftBarButtonItemsur, navigationItemil appelle mon action, mais le bouton ressemble à un bouton rond au lieu de celui avec une flèche vers l'arrière:

self.navigationItem.leftBarButtonItem = backButton;

Comment puis-je lui faire appeler mon action personnalisée avant de revenir à la vue racine? Existe-t-il un moyen de remplacer l'action de retour par défaut, ou existe-t-il une méthode qui est toujours appelée lorsque vous quittez une vue ( viewDidUnloadne le fait pas)?

Perroquets
la source
action: @selector (accueil)]; a besoin d'un: après l'action du sélecteur: @selector (home :)]; sinon ça ne fonctionnera pas
PartySoft
7
@PartySoft Ce n'est pas vrai à moins que la méthode ne soit déclarée avec les deux points. Il est parfaitement valable que les boutons appellent des sélecteurs qui ne prennent aucun paramètre.
mbm29414
3
Pourquoi Apple ne fournirait-il pas un bouton avec un style en forme de bouton arrière? Cela semble assez évident.
JohnK
Regardez la solution dans ce fil
Jiri Volejnik

Réponses:

363

Essayez de placer ceci dans le contrôleur de vue où vous souhaitez 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];
}
William Jockusch
la source
1
c'est une solution de contournement lisse, propre, agréable et très bien pensée
boliva
6
+1 super hack, mais n'offre pas de contrôle sur l'animation de la pop
matm
3
Cela ne fonctionne pas pour moi si j'envoie un message au délégué via un bouton et que le délégué fait apparaître le contrôleur - cela se déclenche toujours.
SAHM
21
Un autre problème est que vous ne pouvez pas différencier si l'utilisateur a appuyé sur le bouton de retour ou si vous avez appelé par programme [self.navigationController popViewControllerAnimated: YES]
Chase Roberts
10
Just an FYI: Swift version:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
hEADcRASH
177

J'ai implémenté l' extension UIViewController-BackButtonHandler . Il n'a pas besoin de sous-classer quoi que ce soit, il suffit de le mettre dans votre projet et de remplacer la navigationShouldPopOnBackButtonméthode dans la UIViewControllerclasse:

-(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échargez un exemple d'application .

gris
la source
16
C'est la solution la plus propre que j'ai vue, meilleure et plus simple que d'utiliser votre propre UIButton personnalisé. Merci!
ramirogm
4
Ce navigationBar:shouldPopItem:n'est pas une méthode privée car elle fait partie du UINavigationBarDelegateprotocole.
onegray
1
mais UINavigationController implémente-t-il déjà le délégué (à retourner YES)? ou le fera-t-il dans le futur? le sous-classement est probablement une option plus sûre
Sam
8
Je viens de mettre en œuvre ceci (plutôt cool BTW) dans iOS 7.1 et j'ai remarqué qu'après le retour, NOle bouton de retour reste dans un état désactivé (visuellement, car il reçoit et réagit toujours aux événements tactiles). Je l'ai contourné en ajoutant une elsedéclaration à la shouldPopvérification et en parcourant les sous-vues de la barre de navigation, et en remettant la alphavaleur à 1 si nécessaire dans un bloc d'animation: gist.github.com/idevsoftware/9754057
boliva
2
C'est l'une des meilleures extensions que j'aie jamais vues. Merci beaucoup.
Srikanth
42

Contrairement à Amagrammer dit, c'est possible. Vous devez sous-classer votre navigationController. J'ai tout expliqué ici (y compris le code d'exemple).

HansPinckaers
la source
La documentation d'Apple ( developer.apple.com/iphone/library/documentation/UIKit/… ) indique que "Cette classe n'est pas destinée au sous-classement". Bien que je ne sois pas sûr de ce qu'ils veulent dire par là - ils pourraient signifier "vous ne devriez normalement pas avoir besoin de faire ça", ou ils pourraient signifier "nous rejetterons votre application si vous jouez avec notre manette" ...
Kuba Suder
C'est certainement la seule façon de le faire. J'aimerais pouvoir vous attribuer plus de points Hans!
Adam Eberbach
1
Pouvez-vous réellement empêcher une vue de se fermer en utilisant cette méthode? Que feriez-vous renvoyer la méthode popViewControllerAnimated si vous vouliez que la vue ne se ferme pas?
JosephH
1
Oui vous pouvez. N'appelez pas la méthode superclass dans votre implémentation, soyez conscient! Vous ne devriez pas faire cela, l'utilisateur s'attend à revenir dans la navigation. Ce que vous pouvez faire, c'est demander une confirmation. Selon la documentation d'Apples, popViewController renvoie: "Le contrôleur de vue qui a été extrait de la pile." Ainsi, quand rien n'est sauté, vous devriez retourner nul;
HansPinckaers
1
@HansPickaers Je pense que votre réponse sur le fait d'empêcher une vue de sortir peut être quelque peu incorrecte. Si j'affiche un message de «confirmation» de l'implémentation des sous-classes de popViewControllerAnimated :, le NavigationBar s'anime toujours d'un niveau dans l'arborescence indépendamment de ce que je retourne. Cela semble être dû au fait que cliquer sur le bouton Précédent appelle shouldPopNavigationItem dans la barre de navigation. Je retourne nil de ma méthode de sous-classes comme recommandé.
deepwinter
15

Version Swift:

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

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

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

Ensuite, créez une classe, par exemple 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
    }
}
kgaidis
la source
Peut-être que j'ai raté quelque chose, mais cela ne fonctionne pas pour moi, la méthode performSomeActionOnThePressOfABackButton de l'extension ne s'appelle jamais
Turvy
@FlorentBreton peut-être un malentendu? shouldPopOnBackButtonPressdevrait être appelé tant qu'il n'y a pas de bogues. performSomeActionOnThePressOfABackButtonest juste une méthode inventée qui n'existe pas.
kgaidis
Je l'ai compris, c'est pourquoi j'ai créé une méthode performSomeActionOnThePressOfABackButtondans mon contrôleur pour exécuter une action spécifique lorsque le bouton de retour est enfoncé, mais cette méthode n'a jamais été appelée, l'action est un retour normal
Turvy
1
Je ne travaille pas non plus pour moi. La méthode shouldPop n'est jamais appelée. Avez-vous placé un délégué quelque part?
Tim Autin
@TimAutin Je viens de tester cela à nouveau et il semble que quelque chose a changé. Élément clé pour comprendre que dans a UINavigationController, le navigationBar.delegateest défini sur le contrôleur de navigation. Donc, les méthodes DEVRAIENT être appelées. Cependant, dans Swift, je ne peux pas les faire appeler, même dans une sous-classe. Cependant, je les ai fait appeler en Objective-C, donc j'utiliserais simplement la version Objective-C pour le moment. Peut-être un bug Swift.
kgaidis
5

Ce n'est pas possible de le faire directement. Il existe plusieurs alternatives:

  1. Créez votre propre personnalisation UIBarButtonItemqui valide au toucher et apparaît si le test réussit
  2. Validez le contenu du champ de formulaire à l'aide d'une UITextFieldméthode déléguée, telle que -textFieldShouldReturn:, qui est appelée après avoir appuyé sur le bouton Returnou Donedu clavier

L'inconvénient de la première option est que le style de flèche pointant vers la gauche du bouton Précédent n'est pas accessible à partir d'un bouton de barre personnalisé. Vous devez donc utiliser une image ou utiliser un bouton de style normal.

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

Alex Reynolds
la source
5

Pour certaines raisons de threading, la solution mentionnée par @HansPinckaers ne me convenait pas, mais j'ai trouvé un moyen très plus simple d'attraper un toucher sur le bouton de retour, et je veux épingler ceci ici au cas où cela pourrait éviter des heures de tromperies pour quelqu'un d'autre. L'astuce est vraiment simple: ajoutez simplement un UIButton transparent en tant que sous-vue à votre UINavigationBar, et définissez vos sélecteurs pour lui comme s'il s'agissait du vrai bouton! Voici un exemple utilisant Monotouch et C #, mais la traduction en 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 = string.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, j'ai mis sa couleur de fond au bleu ... Et ça se voit derrière le bouton de retour! Quoi qu'il en soit, il attrape toujours tout contact ciblant le bouton d'origine.

psychopathe
la source
3

Cette technique vous permet de changer le texte du bouton "retour" sans affecter le titre de l'un des contrôleurs de vue ou voir le texte du bouton retour changer pendant l'animation.

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

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];
Jason Moore
la source
3

Le moyen le plus simple

Vous pouvez utiliser les méthodes de délégation de UINavigationController. La méthode willShowViewControllerest appelée lorsque vous appuyez sur le bouton de retour de votre VC.Faites ce que vous voulez lorsque vous appuyez sur de retour

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
Zar E Ahmer
la source
Assurez-vous que votre contrôleur de vue se définit comme le délégué du navigationController hérité et est conforme au protocole UINavigationControllerDelegate
Justin Milo
3

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
    }

}
AutomatonTec
la source
Le remplacement de la méthode navigationShouldPopOnBackButton dans UIViewController ne fonctionne pas - Le programme exécute la méthode parente et non celle remplacée. Une solution pour ça? Quelqu'un a-t-il le même problème?
Pawel Cala
il revient à rootview si retourne true
Pawriwes
@Pawriwes Voici une solution que j'ai écrite et qui semble fonctionner pour moi: stackoverflow.com/a/34343418/826435
kgaidis
3

Nous avons trouvé une solution qui conserve également le style du bouton de retour. 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];


}

Maintenant, fournissez une fonctionnalité au besoin dans la méthode suivante:

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

Tout ce qu'il fait est de couvrir le bouton arrière avec un bouton transparent;)

Sarasranglt
la source
3

Remplacer navigationBar (_ navigationBar: shouldPop) : Ce n'est pas une bonne idée, même si cela fonctionne. pour moi, cela a généré des plantages aléatoires lors du retour. Je vous conseille de simplement remplacer le bouton de retour en supprimant le backButton par défaut de navigationItem et en créant un bouton de retour personnalisé comme ci-dessous:

override func viewDidLoad(){
   super.viewDidLoad()
   
   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...
 
}

========================================

S'appuyant sur les réponses précédentes avec UIAlert en Swift5 dans une Asynchronous manière


protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ())
}

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
      
        if viewControllers.count < navigationBar.items!.count {
            return true
        }
        
        // Check if we have a view controller that wants to respond to being popped
        
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            
            viewController.shouldPopOnBackButtonPress { shouldPop in
                if (shouldPop) {
                    /// on confirm => pop
                    DispatchQueue.main.async {
                        self.popViewController(animated: true)
                    }
                } else {
                    /// on cancel => do nothing
                }
            }
            /// return false => so navigator will cancel the popBack
            /// until user confirm or cancel
            return false
        }else{
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        }
        return true
    }
}

Sur votre manette


extension MyController: NavigationControllerBackButtonDelegate {
    
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ()) {
    
        let msg = "message"
        
        /// show UIAlert
        alertAttention(msg: msg, actions: [
            
            .init(title: "Continuer", style: .destructive, handler: { _ in
                completion(true)
            }),
            .init(title: "Annuler", style: .cancel, handler: { _ in
                completion(false)
            })
            ])
   
    }

}
Brahimm
la source
Pouvez-vous fournir des détails sur ce qui se passe avec la vérification if viewControllers.count <navigationBar.items! .Count {return true} s'il vous plaît?
H4Hugo
// Empêche un problème de synchronisation de faire apparaître trop d'éléments de navigation // et pas assez de contrôleurs de vue ou vice
versa en cas de
2

Je ne crois pas que ce soit possible, facilement. La seule façon que je pense de contourner cela est de créer votre propre image de flèche du bouton de retour à placer là-haut. C'était frustrant pour moi au début mais je vois pourquoi, par souci 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;
Meltemi
la source
2
Oui, le problème est que je veux qu'il ressemble au bouton de retour normal, j'en ai juste besoin pour appeler d'abord mon action personnalisée ...
Perroquets
2

Il existe un moyen plus simple en sous-classant simplement la méthode déléguée de UINavigationBaret en remplaçant la ShouldPopItemméthode .

jazzyjef2002
la source
Je pense que vous voulez dire sous-classer la classe UINavigationController et implémenter une méthode shouldPopItem. Cela fonctionne bien pour moi. Cependant, cette méthode ne doit pas simplement renvoyer OUI ou NON comme vous vous en doutez. Une explication et une solution sont disponibles ici: stackoverflow.com/a/7453933/462162
arlomedia
2

La solution de 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 faire cela.

"Si le nom d'une méthode déclarée dans une catégorie est le même qu'une méthode dans la classe d'origine, ou une méthode dans une autre catégorie sur la même classe (ou même une superclasse), le comportement n'est pas défini quant à l'implémentation de méthode utilisée au moment de l'exécution. Cela risque moins de poser problème si vous utilisez des catégories avec vos propres classes, mais peut entraîner des problèmes lors de l'utilisation de catégories pour ajouter des méthodes à des classes Cocoa ou Cocoa Touch standard. "

user2612791
la source
2

Utilisation de Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}
Murray Sagal
la source
À partir d'iOS 10, et peut-être plus tôt, cela ne fonctionne plus.
Murray Sagal
2

Voici la version Swift 3 de la réponse de @oneway pour capturer l'événement du bouton de retour de la barre de navigation avant qu'il ne soit déclenché. Comme UINavigationBarDelegatene peut pas être utilisé pour UIViewController, vous devez créer un délégué qui sera déclenché lors de l' navigationBar shouldPopappel.

@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 contrôleur de vue, ajoutez la fonction de délégué:

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

J'ai réalisé que nous voulions souvent ajouter un contrôleur d'alerte pour que les utilisateurs décident s'ils veulent revenir en arrière. Si oui, vous pouvez toujours return falseen navigationShouldPopOnBackButton()fonction et fermer votre contrôleur de vue en faisant quelque chose comme ceci:

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")       
}
Lawliet
la source
J'obtiens une erreur: Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' lorsque j'essaye de compiler votre code, pour la ligne if vc.responds(to: #selector(v...Aussi, le self.topViewControllerretourne une option et il y a un avertissement pour cela aussi.
Sankar
FWIW, j'ai corrigé ce code en faisant: let vc = self.topViewController as! MyViewControlleret cela semble fonctionner correctement jusqu'à présent. Si vous pensez que c'est une bonne modification, vous pouvez modifier le code. Aussi, si vous pensez que cela ne devrait pas être fait, je serai heureux de savoir pourquoi. Merci pour ce code. Vous devriez probablement écrire un article de blog à ce sujet, car cette réponse est enterrée selon les votes.
Sankar
@SankarP La raison pour laquelle vous avez cette erreur est que vous n'êtes MyViewControllerpeut-être pas conforme BackButtonDelegate. Plutôt que de forcer le déballage, vous devez le faire guard let vc = self.topViewController as? MyViewController else { return true }pour éviter un éventuel crash.
Lawliet
Merci. Je pense que la déclaration de garde devrait devenir: guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }s'assurer que l'écran se déplace vers la bonne page au cas où il ne peut pas être correctement diffusé. Je comprends maintenant que la navigationBarfonction est appelée dans tous les VC et pas seulement dans le viewcontroller où ce code existe. Peut-être sera-t-il également bon de mettre à jour le code dans votre réponse? Merci.
Sankar
2

Version de Swift 4 iOS 11.3:

Cela s'appuie sur la réponse de kgaidis de 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 {

    }
}
Edward L.
la source
1

En utilisant les variables de cible et d'action que vous laissez actuellement «nil», vous devriez être en mesure de câbler vos boîtes de dialogue de sauvegarde afin qu'elles soient appelées lorsque le bouton est «sélectionné». Attention, cela peut se déclencher à des moments étranges.

Je suis principalement d'accord avec Amagrammer, mais je ne pense pas qu'il serait si difficile de personnaliser le bouton avec la flèche. Je voudrais simplement renommer le bouton de retour, prendre une capture d'écran, photoshop de la taille du bouton nécessaire et avoir cette image en haut de votre bouton.

TahoeWolverine
la source
Je suis d'accord que vous pourriez Photoshop et je pense que je pourrais le faire si je le voulais vraiment, mais j'ai maintenant décidé de changer un peu l'apparence et la sensation pour que cela fonctionne comme je le souhaite.
John Ballinger
Oui, sauf que les actions ne sont pas déclenchées lorsqu'elles sont attachées à backBarButtonItem. Je ne sais pas s'il s'agit d'un bug ou d'une fonctionnalité; il est possible que même Apple ne le sache pas. En ce qui concerne l'exercice de photoshopping, encore une fois, je me méfierais du fait qu'Apple rejetterait l'application pour avoir abusé d'un symbole canonique.
Amagrammer
Attention: cette réponse a été fusionnée à partir d'un doublon.
Shog9 du
1

Vous pouvez essayer d'accéder à l'élément Bouton droit de NavigationBars et définir sa propriété de sélecteur ... voici une référence UIBarButtonItem de référence , une autre chose si cela ne fonctionne pas, définissez l'élément du bouton droit de la barre de navigation sur un UIBarButtonItem personnalisé que vous créer et définir son sélecteur ... j'espère que cela vous aidera

Daniel
la source
Attention: cette réponse a été fusionnée à partir d'un doublon.
Shog9 du
1

Pour un formulaire qui nécessite une entrée utilisateur comme celui-ci, je recommanderais de l'invoquer en tant que "modal" au lieu de faire partie de votre pile de navigation. De cette façon, ils doivent s'occuper des affaires sur le formulaire, puis vous pouvez le valider et le rejeter à l'aide d'un bouton personnalisé. Vous pouvez même concevoir une barre de navigation qui ressemble au reste de votre application mais vous donne plus de contrôle.

Travis M.
la source
Attention: cette réponse a été fusionnée à partir d'un doublon.
Shog9 du
1

Pour intercepter le bouton Retour, couvrez-le simplement avec un UIControl transparent et interceptez les touches.

@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
Jeff
la source
Attention: cette réponse a été fusionnée à partir d'un doublon.
Shog9
1

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 hors du volet Utilitaires et déposez-le sur le côté gauche de la barre de navigation à l'endroit où se trouverait le bouton Précédent. Définissez le libellé sur "Retour". Vous aurez un bouton fonctionnel que vous pourrez lier à votre IBAction et fermer votre viewController. Je fais du travail puis je déclenche une séquence de déroulement et cela fonctionne parfaitement.

Ce qui n'est pas idéal, c'est que ce bouton n'obtient pas la flèche <et ne reporte pas le titre précédent des VC, mais je pense que cela peut être géré. Pour mes besoins, j'ai défini le nouveau bouton Retour pour être un bouton "Terminé" afin que son objectif soit clair.

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

entrez la description de l'image ici

Dan Loughney
la source
1

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
}
zono
la source
1

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];
}
Ivan
la source
1

Version rapide de la réponse de @ onegray

protocol RequestsNavigationPopVerification {
    var confirmationTitle: String { get }
    var confirmationMessage: String { get }
}

extension RequestsNavigationPopVerification where Self: UIViewController {
    var confirmationTitle: String {
        return "Go back?"
    }

    var confirmationMessage: String {
        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 simplement à RequestsNavigationPopVerificationet ce comportement est adopté par défaut.

Adam Waite
la source
1

Utilisation isMovingFromParentViewController

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}
herrk
la source
Pourriez-vous expliquer plus en détail comment cela prouve que le bouton de retour a été appuyé?
Murray Sagal
C'est simple, mais cela ne fonctionne que si vous êtes sûr de revenir à cette vue à partir de n'importe quelle vue enfant que vous pouvez charger. Si l'enfant ignore cette vue au fur et à mesure qu'elle revient au parent, votre code ne sera pas appelé (la vue avait déjà disparu sans avoir quitté le parent). Mais c'est le même problème avec la gestion des événements uniquement lors du déclenchement du bouton Retour, comme demandé par l'OP. Voilà donc une réponse simple à sa question.
CMont
C'est super simple et élégant. J'aime cela. Juste un problème: cela se déclenchera également si l'utilisateur glisse pour revenir en arrière, même s'il annule à mi-chemin. Peut-être qu'une meilleure solution serait d'insérer ce code viewDidDisappear. De cette façon, il ne se déclenchera qu'une fois que la vue aura définitivement disparu.
Phontaine Judd
1

La réponse de @William est correcte cependant, si l'utilisateur lance un geste de balayage pour revenir en arrière, la viewWillDisappearméthode est appelée et ne selfsera même pas dans la pile de navigation (c'est-à-dire self.navigationController.viewControllersqu'elle ne contiendra pas self), même si le balayage n'est pas terminé et le contrôleur de vue n'est pas réellement ouvert. Ainsi, la solution serait de:

  1. Désactivez le geste de balayage pour revenir en arrière viewDidAppearet autorisez uniquement l'utilisation du bouton de retour en utilisant:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
  2. Ou utilisez simplement à la viewDidDisappearplace, 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.
        }
    }
Boherna
la source
0

La solution que j'ai trouvée jusqu'à présent n'est pas très sympa, mais cela fonctionne pour moi. Prenant cette réponse , je vérifie également si je suis programmé ou non:

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

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

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

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
Ferran Maylinch
la source
0

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")
    }
}
Ashish Kakkad
la source