UIButton dans une vue qui a un UITapGestureRecognizer

186

J'ai vue avec un UITapGestureRecognizer. Ainsi, lorsque je tape sur la vue, une autre vue apparaît au-dessus de cette vue. Cette nouvelle vue comporte trois boutons. Lorsque j'appuie maintenant sur l'un de ces boutons, je n'obtiens pas l'action des boutons, je n'obtiens que l'action du geste du toucher. Je ne peux donc plus utiliser ces boutons. Que puis-je faire pour transmettre les événements à ces boutons? La chose étrange est que les boutons sont toujours mis en évidence.

Je ne peux pas simplement supprimer UITapGestureRecognizer après avoir reçu son robinet. Parce qu'avec lui, la nouvelle vue peut également être supprimée. Signifie que je veux un comportement comme les contrôles vidéo plein écran .

V1ru8
la source

Réponses:

257

Vous pouvez définir votre contrôleur ou votre vue (selon celui qui crée le module de reconnaissance de gestes) en tant que délégué du UITapGestureRecognizer. Ensuite, dans le délégué, vous pouvez implémenter -gestureRecognizer:shouldReceiveTouch:. Dans votre implémentation, vous pouvez tester si le toucher appartient à votre nouvelle sous-vue et, si tel est le cas, demander au module de reconnaissance de gestes de l'ignorer. Quelque chose comme ce qui suit:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    // test if our control subview is on-screen
    if (self.controlSubview.superview != nil) {
        if ([touch.view isDescendantOfView:self.controlSubview]) {
            // we touched our control surface
            return NO; // ignore the touch
        }
    }
    return YES; // handle the touch
}
Lily Ballard
la source
Dans mon fichier d'en-tête, j'avais ma vue implémenter UIGestoreRegognizerDelegate, et dans mon .mi ajouté votre code ci-dessus. Le robinet n'entre jamais dans cette méthode, il va directement à mon gestionnaire. Des idées?
kmehta le
1
@kmehta vous avez probablement oublié de définir la propriété de délégué UIGestureRecognizer.
Jusqu'au
Cette réponse peut-elle être généralisée pour traiter tous les cas où une IBAction est impliquée?
Martin Wickman
@Martin IBAction compile jusqu'à voidafin de ne laisser aucune information derrière lors de l'exécution à utiliser pour la détection. Mais ce que vous pouvez faire, c'est remonter la hiérarchie des vues, tester UIControlet revenir NOsi vous trouvez des contrôles.
Lily Ballard
merci pour ça, ça m'aide. si votre bouton est dans un UIImageView, assurez-vous que userInteractionEnabled de l'imageView est défini sur YES.
james075
156

Pour faire suite au suivi de Casey à la réponse de Kevin Ballard:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if ([touch.view isKindOfClass:[UIControl class]]) {
            // we touched a button, slider, or other UIControl
            return NO; // ignore the touch
        }
    return YES; // handle the touch
}

Cela permet essentiellement à tous les types de commandes d'entrée utilisateur comme les boutons, les curseurs, etc.

cdasher
la source
1
C'est une solution flexible qui peut être accompagnée setCancelsTouchesInView = NOde ne pas déclencher le geste de toucher lors de l'interaction avec les commandes. Cependant, vous pouvez mieux l'écrire:return ![touch.view isKindOfClass:[UIControl class]];
griga13
1
Solution Swift 4+: retour! (Touch.view est UIControl)
nteissler
103

J'ai trouvé cette réponse ici: lien

Vous pouvez aussi utiliser

tapRecognizer.cancelsTouchesInView = NO;

Ce qui empêche le module de reconnaissance des taps d'être le seul à capturer tous les taps

UPDATE - Michael a mentionné le lien vers la documentation décrivant cette propriété: cancelsTouchesInView

ejazz
la source
8
Cela devrait être la réponse acceptée de l'OMI. Cela permet à chaque sous-vue de gérer elle-même le robinet et empêche la vue qui a un tabrecognizer attaché de «détourner» le robinet. Pas besoin d'implémenter un délégué si tout ce que vous voulez faire est de rendre une vue cliquable (pour démissionner du premier répondant / masquer le clavier sur les champs de texte etc.) .. génial!
EeKay
4
Cela devrait certainement être la réponse acceptée. Cette réponse m'a amené à lire correctement la documentation Apple , ce qui indique clairement que le module de reconnaissance de gestes empêchera les sous-vues d'obtenir les événements reconnus à moins que vous ne le fassiez.
Michael van der Westhuizen
1
Cela fonctionnait mais maintenant cela ne fonctionne pas pour moi .. peut-être parce que j'ai un scrollView sous le bouton? J'ai dû implémenter le délégué comme dans les réponses ci-dessus.
xissburg
7
Après avoir expérimenté un peu, j'ai remarqué que cela ne fonctionnera que si la vue qui contient le reconnaissance de mouvement est un frère de l'UIControl, et ce ne sera pas le cas si la vue est le parent de l'UIControl.
xissburg
6
Ne fonctionne pas vraiment comme prévu ... OUI, cela empêche le logiciel de reconnaissance du robinet de manger l'événement sur UIControl. Mais il exécute toujours l'action de reconnaissance, qui exécute 2 actions en même temps.
Tek Yin
72

Pour faire suite à la réponse de Kevin Ballard, j'ai eu le même problème et j'ai fini par utiliser ce code:

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

Cela a le même effet mais cela fonctionnera sur n'importe quel UIButton à n'importe quelle profondeur de vue (mon UIButton avait plusieurs vues de profondeur et le délégué de UIGestureRecognizer n'y faisait pas référence.)

Casey
la source
6
Je ne veux pas être une bite ici, mais c'est vraiment OUI et NON. Veuillez utiliser les conventions Cocoa. TRUE / FALSE / NULL est pour le CoreFoundation-Layer.
steipete
d'après ce que j'ai lu, OUI et NON ont en fait été créés pour la lisibilité. la lisibilité est subjective. il découle des conventions de dénomination des méthodes et des propriétés dont tout le monde ne se préoccupe pas trop. si vous voulez une stricte adhésion à la convention de dénomination, alors oui, utilisez OUI et NON pour tout NSWwhat. cependant, je dirai que si vous interrogez les développeurs, vous obtiendrez des opinions mitigées sur lequel de ceux-ci est le plus lisible [button setHidden: YES]; -ou- [bouton setHidden: TRUE];
Pimp Juice McJones
1
Je suppose que ce que j'essaie de dire, c'est que si le principe des conventions de dénomination est la lisibilité, alors je pense qu'il est difficile de nier que c'est subjectif dans certains cas. si vous êtes puriste, vous ne comprendrez pas cette affirmation.
Pimp Juice McJones
2
Cela peut sembler subjectif, mais après un certain temps de programmation Cocoa, vous entrez vraiment dans le flux de code plus sémantiquement précis ... le "TRUE" semble vraiment faux maintenant après des années d'Objective-C, car il ne correspond pas vraiment à "hidden" bien.
Kendall Helmstetter Gelner
Depuis quand pouvez-vous / passez-vous un geste de toucher à un UIButton en tant qu'événement Touch Up Inside UITouch (ce dernier étant l'événement généré en appuyant sur un UIButton)? Il semble duplicatif et incorrect de créer un outil de reconnaissance de geste de toucher pour un bouton comportant 15 événements tactiles déjà associés à un geste de toucher. J'aimerais voir du code, pas des suppositions.
James Bush
10

Dans iOS 6.0 et versions ultérieures, les actions de contrôle par défaut empêchent le chevauchement du comportement de reconnaissance de gestes. Par exemple, l'action par défaut pour un bouton est une simple pression. Si vous disposez d'un outil de reconnaissance de gestes en un seul clic associé à la vue parent d'un bouton et que l'utilisateur appuie sur le bouton, la méthode d'action du bouton reçoit l'événement tactile au lieu de la reconnaissance de gestes. Cela s'applique uniquement à la reconnaissance des gestes qui chevauche l'action par défaut d'un contrôle, qui comprend: .....

De la documentation d'API d'Apple

Jagie
la source
8

Ces réponses étaient incomplètes. J'ai dû lire plusieurs articles sur la façon d'utiliser cette opération booléenne.

Dans votre fichier * .h, ajoutez ceci

@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>

Dans votre fichier * .m, ajoutez ceci

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {

    NSLog(@"went here ...");

    if ([touch.view isKindOfClass:[UIControl class]])
    {
        // we touched a button, slider, or other UIControl
        return NO; // ignore the touch
    }
    return YES; // handle the touch
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.



    //tap gestrure
    UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
    [tapGestRecog setNumberOfTapsRequired:1];
    [self.view addGestureRecognizer:tapGestRecog];


// This line is very important. if You don't add it then your boolean operation will never get called
tapGestRecog.delegate = self;

}


-(IBAction) screenTappedOnce
{
    NSLog(@"screenTappedOnce ...");

}
Sam B
la source
7

J'ai trouvé une autre façon de le faire à partir d' ici . Il détecte le toucher que ce soit à l'intérieur de chaque bouton ou non.

(1) pointInside: withEvent: (2) locationInView:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
       shouldReceiveTouch:(UITouch *)touch {
    // Don't recognize taps in the buttons
    return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
            ![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
            ![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
}
MindMirror
la source
J'ai essayé toutes les autres solutions pour cela, mais l'approche -pointInside: withEvent: était la seule qui fonctionnait pour moi.
Kenny Wyland
5

Swift 5

Bouton sur superview avec tapgesture

 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let _ = touch.view as? UIButton { return false }
    return true
}

Dans mon cas, la mise en œuvre de hitTest a fonctionné pour moi. J'avais une vue de collection avec un bouton

Cette méthode parcourt la hiérarchie des vues en appelant la point(inside:with:)méthode de chaque sous-vue pour déterminer quelle sous-vue doit recevoir un événement tactile. Si point(inside:with:)renvoie true, la hiérarchie de la sous-vue est parcourue de la même manière jusqu'à ce que la vue la plus au premier plan contenant le point spécifié soit trouvée.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard isUserInteractionEnabled else { return nil }

    guard !isHidden else { return nil }

    guard alpha >= 0.01 else { return nil }

    guard self.point(inside: point, with: event) else { return nil }

    for eachImageCell in collectionView.visibleCells {
        for eachImageButton in eachImageCell.subviews {
            if let crossButton = eachImageButton as? UIButton {
                if crossButton.point(inside: convert(point, to: crossButton), with: event) {
                    return crossButton
                }
            }
        }
    }
    return super.hitTest(point, with: event)
}
Shruthi Pal
la source
3

Voici la version Swift de la réponse de Lily Ballard qui a fonctionné pour moi:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if (scrollView.superview != nil) {
        if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
    }
    return true
}
John Riselvato
la source
1

Vous pouvez empêcher UITapGestureRecognizer d'annuler d'autres événements (comme le fait d'appuyer sur votre bouton) en définissant la valeur booléenne suivante:

    [tapRecognizer setCancelsTouchesInView:NO];
Himanshu Mahajan
la source
1

Si votre scénario est comme ceci:

Vous avez une vue simple et quelques UIButtons, contrôles UITextField ajoutés en tant que sous-vues à cette vue. Vous voulez maintenant fermer le clavier lorsque vous touchez n'importe où ailleurs dans la vue, sauf sur les commandes (sous-vues que vous avez ajoutées)

Alors la solution est:

Ajoutez la méthode suivante à votre XYZViewController.m (qui a votre vue)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}
NaveenRaghuveer
la source
J'ai d'abord essayé avec la réponse de @cdasher , mais dans mon cas, même en cliquant sur le bouton, je reçois touch.view comme "vue" dans ViewController mais pas mon contrôle "UIButton". Quelqu'un peut-il dire pourquoi la propriété de vue du toucher ne renvoie pas la vue d'origine dans laquelle le tap s'est produit
NaveenRaghuveer
1
Ne fonctionnera pas si vous avez une vue de défilement ou une autre vue qui revendique les événements tactiles.
lkraider
1

En optimisant la réponse de cdasher, vous obtenez

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch 
{
    return ![touch.view isKindOfClass:[UIControl class]];
}
Ponts d'argile
la source