UITapGestureRecognizer appuyez sur self.view mais ignorez les sous-vues

95

J'ai besoin d'implémenter une fonctionnalité qui invoquera du code lorsque je tapotai deux fois sur self.view (vue de UIViewCotroller). Mais le problème est que j'ai un autre objet d'interface utilisateur sur cette vue et que je ne veux pas attacher d'objet de reconnaissance à tous. J'ai trouvé cette méthode ci-dessous comment faire un geste sur ma vue et je sais comment cela fonctionne. En ce moment, je suis devant un handicap pour choisir la manière de créer ce module de reconnaissance en ignorant la sous-vue. Des idées? Merci.

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[self.view addGestureRecognizer:doubleTap];
Matrosov Alexander
la source
1
Je ne suis pas sûr, mais avez-vous essayé de définir cancelsTouchesInView sur NO sur le module de reconnaissance? so [doubleTap setCancelsTouchesInView: NO];
JDx

Réponses:

144

Vous devez adopter le UIGestureRecognizerDelegateprotocole à l'intérieur de l' selfobjet et appeler la méthode ci-dessous pour vérifier la vue. Dans cette méthode, comparez votre vue touch.viewet renvoyez la valeur booléenne appropriée (Oui / Non). Quelque chose comme ça:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([touch.view isDescendantOfView:yourSubView]) {
        return NO;
    }
    return YES;
}

Edit: S'il vous plaît, vérifiez également la réponse de @ Ian!

Swift 5

// MARK: UIGestureRecognizerDelegate methods, You need to set the delegate of the recognizer
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
     if touch.view?.isDescendant(of: tableView) == true {
        return false
     }
     return true
}
Ravi
la source
une autre question J'ai quelques boutons sur mon point de vue qui doivent fonctionner. comment puis-je leur donner une priorité?
Matrosov Alexander
si vos boutons ont leurs propres outils de reconnaissance de gestes (probablement), -[UIGestureRecognizer requireGestureRecognizerToFail:]fouillez dans leurs
composants
cherchait ça pendant des heures! Merci! ;)
MasterRazer
Si votre iftest échoue, votre implémentation ne renvoie pas un BOOL; pour un style correct, retournez YES après un bloc if (en utilisant { }) ou dans une elsebranche. Merci, cependant, m'a sauvé un peu de lecture.
RobP
2
Une vue est un descendant d'elle-même, donc cela ne fonctionne pas ... (l'approche globale est correcte cependant, voir l'autre réponse)
Ixx
107

Une autre approche consiste simplement à comparer si la vue du toucher est la vue des gestes, car les descendants ne passeront pas la condition. Un monoplace simple et agréable:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == gestureRecognizer.view
}
Ian
la source
2
Cela fonctionne mieux pour moi que la réponse acceptée. Beaucoup plus concis.
Suman Roy
1
@Ian cette méthode n'est pas invoquée lorsque vous appuyez sur la vue.
Praburaj le
1
Grande réponse concise!
scurioni
La réponse acceptée n'a même pas fonctionné mais cela a parfaitement fonctionné pour moi.
Shalin Shah
@Prabu probablement parce que le délégué de la reconnaissance de geste doit être défini avant
Kubba
23

Et pour la variante Swift:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if touch.view.isDescendantOfView(yourSubView){
        return false
    }
    return true
}

Bon à savoir, isDescendantOfViewrenvoie une Booleanvaleur indiquant si le récepteur est une sous-vue d'une vue donnée ou identique à cette vue.

Antoine
la source
1
N'oubliez pas de définir UIGestureRecognizerDelegate dans votre classe!
Fox5150
4
Au lieu de faire cette déclaration if, ne pouvez-vous pas simplement renvoyer ceci? return! touch.view.isDescendant (sur: gestureRecognizer.view) Aussi, nouvelle syntaxe dans swift 3 ^^
EdwardSanchez
12

Avec Swift 5 et iOS 12, UIGestureRecognizerDelegatea une méthode appelée gestureRecognizer(_:shouldReceive:). gestureRecognizer(_:shouldReceive:)a la déclaration suivante:

Demandez au délégué si un outil de reconnaissance de gestes doit recevoir un objet représentant un toucher.

optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool

Le code complet ci-dessous montre une implémentation possible pour gestureRecognizer(_:shouldReceive:). Avec ce code, un tap sur une sous-vue de la ViewControllervue de (y compris imageView) ne déclenchera pas la printHello(_:)méthode.

import UIKit

class ViewController: UIViewController, UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printHello))
        tapGestureRecognizer.delegate = self
        view.addGestureRecognizer(tapGestureRecognizer)

        let imageView = UIImageView(image: UIImage(named: "icon")!)
        imageView.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
        view.addSubview(imageView)

        // ⚠️ Enable user interaction for imageView so that it can participate to touch events.
        // Otherwise, taps on imageView will be forwarded to its superview and managed by it.
        imageView.isUserInteractionEnabled = true
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        // Prevent subviews of a specific view to send touch events to the view's gesture recognizers.
        if let touchedView = touch.view, let gestureView = gestureRecognizer.view, touchedView.isDescendant(of: gestureView), touchedView !== gestureView {
            return false
        }
        return true
    }

    @objc func printHello(_ sender: UITapGestureRecognizer) {
        print("Hello")
    }

}

Une autre implémentation de gestureRecognizer(_:shouldReceive:)peut être:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return gestureRecognizer.view === touch.view
}

Notez cependant que ce code alternatif ne vérifie pas s'il touch.views'agit d'une sous-vue de gestureRecognizer.view.

Imanou Petit
la source
10

Solution rapide complète (le délégué doit être implémenté ET défini pour le ou les reconnaisseurs):

class MyViewController: UIViewController UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(onBaseTapOnly))
        doubleTapRecognizer.numberOfTapsRequired = 2
        doubleTapRecognizer.delegate = self
        self.view.addGestureRecognizer(doubleTapRecognizer)
    }

    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
        if touch.view.isDescendantOfView(self.view){
            return false
        }
        return true
    }

    func onBaseTapOnly(sender: UITapGestureRecognizer) {
        if sender.state == .Ended {
            //react to tap
        }
    }
}
Arru
la source
6

Variante utilisant CGPoint que vous touchez (SWIFT 4.0)

class MyViewController: UIViewController, UIGestureRecognizerDelegate {

  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {

// Get the location in CGPoint
    let location = touch.location(in: nil)

// Check if location is inside the view to avoid
    if viewToAvoid.frame.contains(location) {
        return false
    }

    return true
  }
}
EntraîneurThys
la source
4

Effacer la voie rapide

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == self.view
}
Musa almatri
la source
Ne jamais être appelé dans iOS 13, du moins avec swipeGestureRecognizer.
Chewie The Chorkie
En quoi est-ce différent de la réponse d'Ian?
sweetfa
3

Notez que l' API gestureRecognizer est devenue :

gestureRecognizer (_: shouldReceive :)

Notez en particulier l'indicateur de soulignement (saut) pour l'étiquette externe du premier paramètre.

En utilisant plusieurs des exemples fournis ci-dessus, je n'ai pas reçu l'événement. Ci-dessous, un exemple qui fonctionne pour les versions actuelles de Swift (3+).

public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    var shouldReceive = false
    if let clickedView = touch.view {
        if clickedView == self.view {
            shouldReceive = true;
        }
    }
    return shouldReceive
}
Rico
la source
2

En plus des solutions ci-dessus, n'oubliez pas de vérifier User Interaction Enabledvotre sous-vue.

entrez la description de l'image ici

MrDEV
la source
2

J'ai dû empêcher le geste sur la vue enfant. La seule chose qui a fonctionné est d'autoriser et de conserver la première vue et d'empêcher les gestes dans toutes les vues suivantes:

   var gestureView: UIView? = nil

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if (gestureView == nil || gestureView == touch.view){
            gestureView = touch.view
            return true
        }
        return false
     }
Shay Moreno
la source
1

Swift 4:

touch.view est désormais optionnel, donc basé sur la réponse de @ Antoine:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let touchedView = touch.view, touchedView.isDescendant(of: deductibleBackgroundView) {
        return false
    }
    return true
}
Jonathan Cabrera
la source
0

Si vous ne voulez pas que votre « recogniser double tap » en conflit avec vos boutons et / ou d' autres contrôles, vous pouvez définir selfcomme UIGestureRecognizerDelegateet mettre en œuvre:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
{
    return !(touch.view is UIControl)
}
PDK
la source