UITapGestureRecognizer rompt UITableView didSelectRowAtIndexPath

207

J'ai écrit ma propre fonction pour faire défiler les champs de texte lorsque le clavier apparaît. Afin de fermer le clavier en tapant loin du champ de texte, j'ai créé unUITapGestureRecognizer qui prend soin de démissionner du premier répondeur sur le champ de texte lors du tapotement.

Maintenant, j'ai également créé une saisie semi-automatique pour le UITableViewchamp de texte qui crée juste en dessous du champ de texte et le remplit d'éléments lorsque l'utilisateur entre du texte.

Cependant, lors de la sélection de l'une des entrées dans la table de saisie semi-automatique, didSelectRowAtIndexPathn'est pas appelé. Au lieu de cela, il semble que le module de reconnaissance des gestes du robinet soit appelé et démissionne simplement du premier répondant.

Je suppose qu'il existe un moyen de dire au reconnaisseur de gestes de toucher de continuer à transmettre le message de toucher au UITableView, mais je ne peux pas comprendre ce que c'est. Toute aide serait très appréciée.

Jason
la source
Voir ce fil .
Duncan Babbage,

Réponses:

258

Ok, enfin trouvé après quelques recherches dans les documents de reconnaissance des gestes.

La solution consistait à implémenter UIGestureRecognizerDelegateet à ajouter ce qui suit:

////////////////////////////////////////////////////////////
// UIGestureRecognizerDelegate methods

#pragma mark UIGestureRecognizerDelegate methods

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([touch.view isDescendantOfView:autocompleteTableView]) {

        // Don't let selections of auto-complete entries fire the 
        // gesture recognizer
        return NO;
    }

    return YES;
}

Cela s'en est occupé. J'espère que cela aidera également les autres.

Jason
la source
Vous devez également sauter des tapotements sur le champ de texte probablement.
Steven Kramer
5
J'ai eu plus de chance avecreturn ![touch.view isKindOfClass:[UITableViewCell class]];
Josh Paradroid
1
@Josh Cela ne fonctionne pas si vous appuyez sur les étiquettes à l'intérieur de la cellule. En fait, la réponse de Jason fonctionne parfaitement pour ce dont j'ai besoin.
William T.
Mais ce n'est pas la réponse à votre problème, non? Il s'agit d'un / ou. Fondamentalement, n'importe quelle touche sur la vue de la table est insignifiante avec la reconnaissance des gestes .... ce que vous voulez, c'est que la reconnaissance des gestes ET la sélection de la vue de la table fonctionnent
PostCodeism
5
@Durgaprasad Pour désactiver explicitement la reconnaissance des gestes lors de la sélection des cellules, j'ai ajouté un ![touch.view isEqual: self.tableView] &&avant le [touch.view isDescendantOfView: self.tableView]pour exclure le tableViewlui - même (car isDescendantOfView:retourne également YESsi l'argument-vue est le récepteur-vue lui-même). J'espère que cela aide d'autres personnes qui veulent le même résultat.
anneblue
220

La façon la plus simple de résoudre ce problème est de:

UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc] 
    initWithTarget:self action:@selector(tap:)];
[tapRec setCancelsTouchesInView:NO];

Cela permet de UIGestureRecognizerreconnaître le robinet et également de passer le toucher au répondeur suivant. Une conséquence inattendue de cette méthode est si vous avez un UITableViewCellécran qui pousse un autre contrôleur de vue. Si l'utilisateur appuie sur la ligne pour fermer le clavier, le clavier et le push seront reconnus. Je doute que ce soit votre intention, mais cette méthode convient à de nombreuses situations.

En outre, en développant la réponse de Robert, si vous avez un pointeur sur la table en question, vous pouvez directement comparer sa classe au lieu de devoir la convertir en chaîne et espérer qu'Apple ne changera pas la nomenclature:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
     shouldReceiveTouch:(UITouch *)touch
{
    if([touch.view class] == tableview.class){
        return //YES/NO
    }

    return //YES/NO

}

N'oubliez pas que vous devez également déclarer UIGestureRecognizeravoir un délégué contenant ce code.

TMilligan
la source
4
Merci, setCancelsTouchesInView change tout :)
PostCodeism
Peut vouloir utiliser isDescendantOfViewau lieu de simplement vérifier si le classest égal, car vous pouvez appuyer sur une sous-vue. Dépend de ce dont vous avez besoin.
shim
71

Définissez cancelsTouchesInViewvotre identificateur sur false. Sinon, il "consomme" le toucher pour lui-même et ne le transmet pas à la vue de table. C'est pourquoi l'événement de sélection ne se produit jamais.

par exemple dans swift

let tapOnScreen: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "CheckTheTime")
tapOnScreen.cancelsTouchesInView = false
view.addGestureRecognizer(tapOnScreen)
Abdulrahman Masoud
la source
1
Merci beaucoup pour cela!
Bishan
1
C'est le plus propre de Swift. Merci
Dx_
fonctionne bien dans les cellules de la table, belle et simple!
Abdoelrhman
@laoyur very true
bLacK hoLE
toujours obtenu un clic de cellule au lieu d'un geste de robinet
famfamfam
42

Et pour Swift (basé sur la réponse de @Jason):

class MyAwesomeClass: UIViewController, UIGestureRecognizerDelegate

private var tap: UITapGestureRecognizer!

override func viewDidLoad() {
   super.viewDidLoad()

   self.tap = UITapGestureRecognizer(target: self, action: "viewTapped:")
   self.tap.delegate = self
   self.view.addGestureRecognizer(self.tap)
}

// UIGestureRecognizerDelegate method
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if touch.view?.isDescendantOfView(self.tableView) == true {
        return false
    }
    return true
}
Nikolaj Nielsen
la source
Merci beaucoup, cela m'a fait gagner du temps.
Bishan
22

Je peux avoir une meilleure solution pour ajouter un geste de tapotement sur une vue de table mais en permettant la sélection de cellules en même temps:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if gestureRecognizer is UITapGestureRecognizer {
        let location = touch.locationInView(tableView)
        return (tableView.indexPathForRowAtPoint(location) == nil)
    }
    return true
}

Je cherche juste une cellule au point de l'écran où l'utilisateur tape. Si aucun chemin d'index n'est trouvé, je laisse le geste recevoir le toucher sinon je l'annule. Pour moi, cela fonctionne très bien.

bonokite
la source
1
Cette solution est géniale! Très utile pour distinguer la cellule du champ vide du tableau ..
Alessandro Ornano
18

Je pense qu'il n'est pas nécessaire d'écrire des blocs de codes simplement définis cancelsTouchesInViewsur falsevotre objet gestuel, par défaut c'est trueet vous n'avez qu'à le définir false. Si vous utilisez un UITapGestureobjet dans votre code et utilisez également UIScrollView(tableview, collectionview), définissez cette propriétéfalse

let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
SHUBHAYAN KUNDU
la source
Merci d'avoir posté cette réponse
Gustavo Baiocchi Costa
14

Une solution similaire consiste à mettre en œuvre gestureRecognizer:shouldReceiveTouch: à l'aide de la classe de la vue pour déterminer l'action à entreprendre. Cette approche a l'avantage de ne pas masquer les taps dans la région entourant directement la table (les vues de cette zone descendent toujours des instances UITableView, mais elles ne représentent pas les cellules).

Cela a également un bonus qu'il fonctionne avec plusieurs tables sur une seule vue (sans ajouter de code supplémentaire).

Mise en garde: il est supposé qu'Apple ne changera pas le nom de classe.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    return ![NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"];
}
Robert Altman
la source
1
Pour éliminer la chaîne de nom de classe, utilisez cette lignereturn ![[touch.view.superview class] isSubclassOfClass:[UITableViewCell class]];
Nianliang
7

Pour Swift 4.2, la solution consistait à implémenter UIGestureRecognizerDelegateet à ajouter ce qui suit:

extension ViewController : UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if touch.view!.isDescendant(of: tblView) {
            return false
        }
        return true
    }
}

lorsque vous cliquez sur la vue de table, cette méthode déléguée renvoie false et la didSelectRowAtIndexPathméthode de vue de table fonctionne correctement.

Ashish Kanani
la source
1
merci mon pote d'avoir posté cette réponse, vous économisez mon temps.
Jay Bhalani
4

J'ai eu une situation différente où je voulais que la fonction de geste tactile soit appelée uniquement lorsque l'utilisateur tapait à l' extérieur de la vue de table. Si l'utilisateur a tapé à l'intérieur de la vue de table, la fonction de geste tactile ne doit pas être appelée. En outre, si la fonction de geste tactile est appelée, elle doit toujours transmettre l'événement tactile à la vue sur laquelle vous avez tapé plutôt que de le consommer.

Le code résultant est une combinaison de la réponse d'Abdulrahman Masoud et de la réponse de Nikolaj Nielsen.

extension MyViewController: UIGestureRecognizerDelegate {
    func addGestureRecognizer() {
        let tapOnScreen = UITapGestureRecognizer(target: self,
                                                action: #selector(functionToCallWhenUserTapsOutsideOfTableView))

        // stop the gesture recognizer from "consuming" the touch event, 
        // so that the touch event can reach other buttons on view.
        tapOnScreen.cancelsTouchesInView = false

        tapOnScreen.delegate = self

        self.view.addGestureRecognizer(tapOnScreen)
    }

    // if your tap event is on the menu, don't run the touch event.
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if touch.view?.isDescendant(of: self.tableView) == true {
            return false
        }
        return true
    }

    @objc func functionToCallWhenUserTapsOutsideOfTableView() {

        print("user tapped outside table view")
    }
}

Et dans la MyViewControllerclasse, la classe qui a le UITableView, dans le onViewDidLoad(), je me suis assuré d'appeler addGestureRecognizer():

class MyViewController: UIViewController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        self.addGestureRecognizer()
        ...
    }
    ...
}
Ali Mizan
la source
3

Il suffit de définir cancels touches in viewà falsepour tout geste sous le (table/collection)View:

- Interfacebuilder

Interfacebuilder

- Code

<#gestureRecognizer#>.cancelsTouchesInView = false
Mojtaba Hosseini
la source
que se passe-t-il si je veux désactiver son travail, ce qui permet non seulement à didSelectRowAt de fonctionner avec lui?
Eyad Shokry
1

Bien qu'il soit tard et que beaucoup de gens trouvent que les suggestions ci-dessus fonctionnent bien, je n'ai pas pu faire fonctionner les méthodes de Jason ou TMilligan.

J'ai une tableView statique avec plusieurs cellules contenant des textFields qui reçoivent des entrées numériques en utilisant uniquement le clavier numérique. C'était idéal pour moi:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if(![touch.view isKindOfClass:[UITableViewCell class]]){

        [self.firstTF resignFirstResponder];
        [self.secondTF resignFirstResponder];
        [self.thirdTF resignFirstResponder];
        [self.fourthTF resignFirstResponder];

        NSLog(@"Touches Work ");

        return NO;
    }
    return YES;
}

Assurez-vous que vous l'avez implémenté <UIGestureRecognizerDelegate>dans votre fichier .h.

Cette ligne ![touch.view isKindOfClass:[UITableViewCell class]]vérifie si une tableViewCell a été tapée et rejette tout clavier actif.

App Dev Guy
la source
1

Une solution simple utilise des instances UIControl dans UITableViewCell pour obtenir des contacts. Vous pouvez ajouter des vues avec userInteractionEnables == NO à UIControl pour obtenir des taps.

Andrew Romanov
la source
0

Voici ma solution, qui lie directement shouldReceiveTouch du module de reconnaissance à l'affichage du clavier.

Dans votre délégué de reconnaissance des gestes tactiles:

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([PFXKeyboardStateListener sharedInstance].visible) {
        return YES;
    }

    return NO;
}

Et le PFXKeyboardStateListener.h:

@interface PFXKeyboardStateListener : NSObject
{
    BOOL _isVisible;
}

+ (PFXKeyboardStateListener *)sharedInstance;

@property (nonatomic, readonly, getter=isVisible) BOOL visible;

@end

Et le PFXKeyboardStateListener.m:

static PFXKeyboardStateListener *sharedInstance;

@implementation PFXKeyboardStateListener

+ (PFXKeyboardStateListener *)sharedInstance
{
    return sharedInstance;
}

+ (void)load
{
    @autoreleasepool {
        sharedInstance = [[self alloc] init];
    }
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (BOOL)isVisible
{
    return _isVisible;
}

- (void)didShow
{
    _isVisible = YES;
}

- (void)didHide
{
    _isVisible = NO;
}

- (id)init
{
    if ((self = [super init])) {
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(didShow) name:UIKeyboardDidShowNotification object:nil];
        [center addObserver:self selector:@selector(didHide) name:UIKeyboardWillHideNotification object:nil];
    }
    return self;
}

@end

Vous voudrez peut-être mettre à jour le modèle singleton de l'écouteur de clavier, je n'y suis pas encore arrivé. J'espère que cela fonctionne pour tout le monde ainsi que pour moi. ^^

bgfriend0
la source
0

Implémentez cette méthode pour déléguer UIGestureRecognizer:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch
{
  UIView *superview = touch.view;
  do {
    superview = superview.superview;
    if ([superview isKindOfClass:[UITableViewCell class]])
      return NO;
  } while (superview && ![superview isKindOfClass:[UITableView class]]);

  return superview != nil;
}
cxa
la source
0

En rapide, vous pouvez l'utiliser à l'intérieur

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if CheckTheTime() == true {
        // do something 
    }else{
    }
}

func CheckTheTime() -> Bool{
    return true
}
Abdulrahman Masoud
la source
0

Mon cas était différent, y compris une barre de recherche et uitableview sur self.view. Je voulais ignorer le clavier uisearchbar en touchant la vue.

var tapGestureRecognizer:UITapGestureRecognizer?

override func viewWillAppear(animated: Bool) {
    tapGestureRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleTap:"))
}

Sur les méthodes déléguées UISearchBar:

func searchBarShouldBeginEditing(searchBar: UISearchBar) -> Bool {
    view.addGestureRecognizer(tapGestureRecognizer!)
    return true
}
func searchBarShouldEndEditing(searchBar: UISearchBar) -> Bool {
    view.removeGestureRecognizer(tapGestureRecognizer!)
    return true
}

Lorsque l'utilisateur touche à self.view:

func handleTap(recognizer: UITapGestureRecognizer) {
    sampleSearchBar.endEditing(true)
    sampleSearchBar.resignFirstResponder()
}
AG
la source
0

Swift 5, mai 2020.

J'ai un textField et une tableView qui deviennent visibles lorsque j'entre du texte.

État initial Je souhaite donc 2 événements différents lorsque j'appuie sur tableViewCell ou autre chose.

Le clavier et la table sont affichés

Nous ajoutons d'abord tapGestureRecognizer.

tap = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
tap.delegate = self
view.addGestureRecognizer(tap)

@objc func viewTapped() {
        view.endEditing(true)
}

Ensuite, nous ajoutons la vérification suivante dans UIGestureRecognizerDelegate:

extension StadtViewController: UIGestureRecognizerDelegate {

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if touch.view?.isDescendant(of: self.tableView) == true {
        return false
    } else {
        view.endEditing(true)
        return true
    }
}

}

Si je veux d'abord masquer le clavier, la tableView reste visible et sensible à mes appuis.

entrez la description de l'image ici

Turquie-homme
la source
-1

Ceci est ma solution basée sur les réponses ci-dessus ... Cela a fonctionné pour moi ...

//Create tap gesture for menu transparent view
UITapGestureRecognizer *rightTableTransparentViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(rightTableTransparentViewTapMethod:)];
[rightTableTransparentViewTap setCancelsTouchesInView:NO];
[_rightTableTransparentView addGestureRecognizer:rightTableTransparentViewTap];

- (void)rightTableTransparentViewTapMethod:(UITapGestureRecognizer *)recognizer {
    //Write your code here
}
iOS
la source
-1

PROBLÈME: Dans mon cas, le problème était que j'avais initialement placé un bouton dans chaque cellule collectionView et défini les contraintes pour remplir la cellule, de sorte que lorsque la cellule était cliquée, elle cliquait sur le bouton, mais la fonction des boutons était vide, donc rien n'était semble se produire.

CORRECTIF: j'ai résolu ce problème en supprimant le bouton de la cellule de la vue de la collection.

Marcus Levi
la source