Comment désactiver le geste de balayage arrière dans UINavigationController sur iOS 7

326

Dans iOS 7, Apple a ajouté un nouveau comportement de navigation par défaut. Vous pouvez balayer depuis le bord gauche de l'écran pour revenir sur la pile de navigation. Mais dans mon application, ce comportement est en conflit avec mon menu de gauche personnalisé. Alors, est-il possible de désactiver ce nouveau geste dans UINavigationController?

ArtFeel
la source
2
J'ai également découvert que si vous définissez navigationItem.hidesBackButton = true, ce geste est également désactivé. Dans mon cas, j'ai implémenté un bouton de retour personnalisé et l'ajout en tant queleftBarButtonItem
Umair

Réponses:

586

J'ai trouvé une solution:

Objectif c:

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

Swift 3+:
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false

ArtFeel
la source
29
Bien sûr, vous devez vérifier la disponibilité de nouvelles méthodes si vous prenez en charge les anciennes versions d'iOS.
ArtFeel
2
Existe-t-il un moyen de le désactiver pour une partie de la vue?
Marc
11
Vous pouvez enable / disablereconnaître sur viewDidAppear:/ viewDidDisappear. Ou, vous pouvez implémenter le UIGestureRecognizerDelegateprotocole avec votre logique plus complexe et le définir comme recognizer.delegatepropriété.
ArtFeel
26
Sur iOS8, la mise en self.navigationController.interactivePopGestureRecognizer.enabledpropriété ne fonctionne pas en suivant les méthodes de vue: viewDidLoad, viewWillAppear, viewDidAppear, viewDidDisappear, mais les travaux de méthode viewWillDisappear. Sur iOS7, il fonctionne dans toutes les méthodes mentionnées ci-dessus. Essayez donc de l'utiliser dans toutes les autres méthodes tout en travaillant sur le viewController, je confirme qu'il fonctionne pour moi sur iOS8 lorsque je clique sur un bouton à l'intérieur de la vue.
Sihad Begovic
8
Peut confirmer que cela ne fonctionnera pas dans iOS8 dans viewDidLoad et viewWillAppear, en le mettant dans viewwilllayoutgubviews a fait l'affaire
tonytastic
47

J'ai découvert que le fait de désactiver le geste uniquement ne fonctionnait pas toujours. Cela fonctionne, mais pour moi, cela ne s'est produit qu'après avoir utilisé la backgesture. La deuxième fois, cela ne déclencherait pas la backgesture.

Le correctif pour moi était de déléguer le geste et d'implémenter la méthode shouldbegin pour retourner NO:

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

    // Disable iOS 7 back gesture
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        self.navigationController.interactivePopGestureRecognizer.delegate = self;
    }
}

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

    // Enable iOS 7 back gesture
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = YES;
        self.navigationController.interactivePopGestureRecognizer.delegate = nil;
    }
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    return NO;
}
Antoine
la source
1
Merci! Cela est nécessaire pour désactiver complètement le balayage arrière. Il existe toujours dans iOS 8 et sent comme un bogue Apple.
Eric Chen
Merci, semble être la seule chose qui a fonctionné.
Ben
Je ne sais pas pourquoi mais un contrôleur de vue dans mon application pour une raison inconnue s'est écrasé sur ce geste du dos .. cela m'a évité de le trouver car je n'avais pas besoin de ce geste du dos et j'ai donc désactivé en utilisant ce code .. +1
Ahsan Ebrahim
1
@AhsanEbrahim, lorsque le geste de retour commence, viewWillAppearest appelé sur la vue derrière la vue actuelle. Cela peut provoquer des ravages dans la logique du code car la vue actuelle est toujours active. Pourrait être la cause de votre crash.
phatmann
Les enabledlignes oui / non sont-elles nécessaires? Vous revenez NOde gestureRecognizerShouldBegin, n'est pas ce suffisant?
ToolmakerSteve
30

Supprimez simplement la reconnaissance des gestes de NavigationController. Travaillez dans iOS 8.

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    [self.navigationController.view removeGestureRecognizer:self.navigationController.interactivePopGestureRecognizer];
Vladimir Samoylov
la source
la seule solution qui fonctionne réellement sous iOS 8 et 9
Kappe
7
Fonctionne également dans iOS 10, cela devrait être la réponse acceptée. Soit dit en passant, si vous souhaitez le réactiver, faites [self.navigationController.view addGestureRecognizer:self.navigationController.interactivePopGestureRecognizer]quelque part.
ooops
22

Depuis iOS 8, la réponse acceptée ne fonctionne plus. Je devais arrêter le balayage pour ignorer le geste sur mon écran de jeu principal, j'ai donc implémenté ceci:

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

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

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.delegate = nil;
    }

}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
     return NO;
}
Charlie Seligman
la source
2
Bien que cela fonctionne avec iOS8, je reçois un avertissement sur la ligne * .delegate = self; indiquant: Affectation à l'ID <UIGestureRecognizerDelegate> 'du type incompatible' ViewController * const __strong '
David Douglas
2
Depuis iOS8, la réponse acceptée fonctionne toujours comme prévu. Vous faites probablement autre chose de mal ..
Alexandre G
Géré pour le faire fonctionner semi en appelant la réponse acceptée dans viewWillLayoutSubviews. Cependant, en glissant, la page a de nouveau appelé «viewDidLoad», donc je suis revenu à ma réponse ci
Charlie Seligman
@DavidDouglas: vous pourriez peut-être éliminer l'avertissement avec ce code: __weak __typeof (self) theSafeSelf = self? Définissez ensuite le délégué sur theSafeSelf.
lifjoy
1
@DavidDouglas: Vous devez ajouter <UIGestureRecognizerDelegate> à l'interface pour se débarrasser de cet avertissement
primehalo
20

J'ai un peu raffiné la réponse de Twan, car:

  1. votre contrôleur de vue peut être défini comme délégué à d'autres reconnaisseurs de gestes
  2. définir le délégué sur nilconduit à des problèmes de blocage lorsque vous revenez au contrôleur de vue racine et effectuez un geste de balayage avant de naviguer ailleurs.

L'exemple suivant suppose iOS 7:

{
    id savedGestureRecognizerDelegate;
}

- (void)viewWillAppear:(BOOL)animated
{
    savedGestureRecognizerDelegate = self.navigationController.interactivePopGestureRecognizer.delegate;
    self.navigationController.interactivePopGestureRecognizer.delegate = self;
}

- (void)viewWillDisappear:(BOOL)animated
{
    self.navigationController.interactivePopGestureRecognizer.delegate = savedGestureRecognizerDelegate;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if (gestureRecognizer == self.navigationController.interactivePopGestureRecognizer) {
        return NO;
    }
    // add whatever logic you would otherwise have
    return YES;
}
Jack
la source
+1 "définir le délégué sur zéro entraîne des problèmes de blocage lorsque vous revenez au contrôleur de vue racine et effectuez un mouvement de balayage avant de naviguer ailleurs."
albertamg
10

Veuillez définir ceci dans root vc:

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:YES];
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;

}

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:YES];
    self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
reza_khalafi
la source
9

Pour Swift:

navigationController!.interactivePopGestureRecognizer!.enabled = false
iPhone 7
la source
12
Cela fonctionne, même si je suggère d'utiliser le chaînage facultatif au lieu du déballage forcé. par exemple self.navigationController? .interactivePopGestureRecognizer? .isEnabled = false
Womble
5

cela fonctionne pour moi dans ios 10 et versions ultérieures:

- (void)viewWillAppear:(BOOL)animated {
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }

}

cela ne fonctionne pas sur la méthode viewDidLoad ().

Logique
la source
5

ÉDITER

Si vous souhaitez gérer la fonction de balayage arrière pour des contrôleurs de navigation spécifiques, pensez à utiliser SwipeBack .

Avec cela, vous pouvez définir navigationController.swipeBackEnabled = NO .

Par exemple:

#import <SwipeBack/SwipeBack.h>

- (void)viewWillAppear:(BOOL)animated
{
    navigationController.swipeBackEnabled = NO;
}

Il peut être installé via CocoaPods .

pod 'SwipeBack', '~> 1.0'

Je m'excuse par manque d'explication.

devxoul
la source
6
Lorsque vous faites la promotion d'un projet auquel vous participez, vous devez divulguer votre affiliation avec celui-ci.
2
De plus, le seul but de votre projet est d'activer manuellement le geste de balayage lorsque le système par défaut ne fonctionne pas, tandis que la question demande comment désactiver ce geste à l'échelle du système, donc même si vous définissez, self.navigationController.swipeBackEnabled = NOje suis presque sûr que cela ne désactivera que votre le mouvement de balayage de la bibliothèque, mais celui du système sera toujours activé.
1
Désolé pour ma réponse courte, je viens de modifier ma réponse avec des informations supplémentaires: "utile pour des contrôleurs de navigation spécifiques". Merci!
devxoul
Il semble utiliser swizzle qui n'est plus autorisé. iOS8?
Matt
1
@devxoul Je suis désolé! Je pensais avoir lu quelque chose il y a quelque temps en disant que le swizzling n'était plus autorisé. Cependant, je ne trouve rien qui dise cela. Je suppose que je me trompe.
Matt
4

Ma méthode. Un reconnaisseur de gestes pour les gouverner tous:

class DisabledGestureViewController: UIViewController: UIGestureRecognizerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController!.interactivePopGestureRecognizer!.delegate = self
    }

    func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
        // Prevent going back to the previous view
        return !(navigationController!.topViewController is DisabledGestureViewController)
    }
}

Important: ne réinitialisez le délégué nulle part dans la pile de navigation: navigationController!.interactivePopGestureRecognizer!.delegate = nil

SoftDesigner
la source
3

C'est la voie sur Swift 3

travaille pour moi

    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
Tayo119
la source
3

Toutes ces solutions manipulent le reconnaisseur de gestes d'Apple d'une manière qu'elles ne recommandent pas. Un ami vient de me dire qu'il existe une meilleure solution:

[navigationController.interactivePopGestureRecognizer requireGestureRecognizerToFail: myPanGestureRecognizer];

où myPanGestureRecognizer est le module de reconnaissance de gestes que vous utilisez pour, par exemple, afficher votre menu. De cette façon, le reconnaisseur de gestes d'Apple n'est pas réactivé par eux lorsque vous appuyez sur un nouveau contrôleur de navigation et vous n'avez pas besoin de compter sur des retards hackers qui peuvent se déclencher trop tôt si votre téléphone est mis en veille ou sous une lourde charge.

Laissant cela ici parce que je sais que je ne m'en souviendrai pas la prochaine fois que j'en aurai besoin, et alors j'aurai la solution au problème ici.

uliwitness
la source
3

swift 5, swift 4.2 peut utiliser le code ci-dessous.

// disable
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
// enable
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
Zgpeace
la source
2

Aucune des réponses données ne m'a aidé à résoudre le problème. Publier ma réponse ici; peut être utile pour quelqu'un

Déclarez private var popGesture: UIGestureRecognizer?comme variable globale dans votre viewcontroller. Puis implémentez le code dans les méthodes viewDidAppear et viewWillDisappear

override func viewDidAppear(animated: Bool) {

    super.viewDidAppear(animated)

    if self.navigationController!.respondsToSelector(Selector("interactivePopGestureRecognizer")) {

        self.popGesture = navigationController!.interactivePopGestureRecognizer
        self.navigationController!.view.removeGestureRecognizer(navigationController!.interactivePopGestureRecognizer!)
    }
}


override func viewWillDisappear(animated: Bool) {

    super.viewWillDisappear(animated)

    if self.popGesture != nil {
        navigationController!.view.addGestureRecognizer(self.popGesture!)
    }
}

Cela désactivera le balayage dans iOS v8.x et suivants

Augustine PA
la source
J'essaie d'imaginer dans quelles circonstances cela fonctionnerait, mais pas celui de Jack. Vous dites que vous avez essayé toutes les autres réponses: qu'est-ce qui a mal tourné lorsque vous avez essayé celui de Jack?
ToolmakerSteve
D'un autre côté, cela semble plus simple que celui de Jack, alors ce n'est peut-être pas important. J'ai décidé que j'aime ça, car je n'ai pas à déclarer ma classe en tant que délégué, ni à manipuler interactivePopGestureRecognizer.delegate.
ToolmakerSteve
BTW, le code peut être simplifié. Retirez if( .. respondsToSelector ... La ligne suivante définit popGesture sur un module de reconnaissance ou sur zéro. Ensuite , utilisez sa valeur: if (self.popGesture != nil) self.navigationController .. removeGestureRecognizer( self.popGesture ).
ToolmakerSteve
2

Cela fonctionne viewDidLoad:pour iOS 8:

  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      self.navigationController.interactivePopGestureRecognizer.enabled = false;
  });

Beaucoup de problèmes pourraient être résolus avec l'aide du bon vieux dispatch_after .

Bien que veuillez noter que cette solution est potentiellement dangereuse, veuillez utiliser votre propre raisonnement.

Mettre à jour

Pour iOS 8.1, le délai devrait être de 0,5 seconde

Sur iOS 9.3, aucun délai n'est plus nécessaire, cela fonctionne simplement en le plaçant dans votre viewDidLoad:
(À déterminer si fonctionne sur iOS 9.0-9.3)

navigationController?.interactivePopGestureRecognizer?.enabled = false
Dannie P
la source
À moins que vous ne sachiez quand le logiciel de reconnaissance des gestes est installé sur la vue, attendre un temps arbitraire pour le désactiver peut ou peut ne pas fonctionner.
kalperin
@kalperin ce n'est pas garanti de fonctionner, bien que ce soit une solution très pratique à certains moments. Utilisez votre propre raisonnement.
Dannie P
Cela fonctionne pour moi ayant une version supérieure à iOS 8.1 :)
iChirag
viewDidLoadplus le retard est une pratique de programmation risquée. Une mauvaise habitude pour commencer. Que se passe-t-il si l'utilisateur commence le balayage avant que votre appel retardé ne se déclenche? Il n'y a pas de temps sûr qui soit suffisamment long mais pas trop long. C'est pourquoi d'autres réponses, postées bien avant la vôtre, suggèrent d'insérer le code viewDidAppear. Cela garantit que tout est installé. N'inventez pas de retards arbitraires; utiliser la séquence d'appels d'Apple comme prévu.
ToolmakerSteve
1
@iChirag true. J'ai noté que pour 8.1, vous avez besoin d'un délai de 0,5 seconde
Dannie P
1

Pour Swift 4, cela fonctionne:

class MyViewController: UIViewController, UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationController?.interactivePopGestureRecognizer?.gesture.delegate = self
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)

        self.navigationController?.interactivePopGestureRecognizer?.gesture.isEnabled = false
    }

}
Mat0
la source
Vous ne devez pas remplacer le délégué de geste pop interactif car cela entraînera un comportement non documenté
Josh Bernfeld
Je pense que ce n'est pas vraiment remplacer le délégué mais simplement modifier la variable booléenne qu'ils ont fournie à cet effet, donc ce ne sera pas un problème
Lok SN
0

Cela a fonctionné pour moi pour la plupart des contrôleurs de vue.

self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false

Cela ne fonctionnait pas pour certains viewcontrollers comme UIPageViewController. Sur pagecontentviewcontroller UIPageViewController ci-dessous, le code a fonctionné pour moi.

override func viewDidLoad() {
   self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
   self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
override func viewWillDisappear(_ animated: Bool) {
   self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
   self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
}

Sur UIGestureRecognizerDelegate,

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
   if gestureRecognizer == self.navigationController?.interactivePopGestureRecognizer {
      return false
}
      return true
}
Faris Muhammed
la source