UITapGestureRecognizer interrompe UITableView didSelectRowAtIndexPath


207

Ho scritto la mia funzione per scorrere i campi di testo quando appare la tastiera. Per chiudere la tastiera toccando lontano dal campo di testo, ho creato un oggetto UITapGestureRecognizerche si occupa di dimettersi dal primo risponditore sul campo di testo quando si tocca via.

Ora ho anche creato un completamento automatico per il campo di testo che crea un campo UITableViewappena sotto il campo di testo e lo popola con gli elementi mentre l'utente inserisce il testo.

Tuttavia, quando si seleziona una delle voci nella tabella completata automaticamente, didSelectRowAtIndexPathnon viene chiamato. Invece, sembra che il riconoscimento dei gesti dei tocchi venga chiamato e si dimetta dal primo risponditore.

Immagino che ci sia un modo per dire al riconoscitore dei gesti dei tocchi di continuare a passare il messaggio di tocco al UITableView, ma non riesco a capire di cosa si tratti. Qualsiasi aiuto sarebbe molto apprezzato.


Risposte:


258

Ok, finalmente l'ho trovato dopo aver cercato nei documenti di riconoscimento dei gesti.

La soluzione era implementare UIGestureRecognizerDelegatee aggiungere quanto segue:

////////////////////////////////////////////////////////////
// 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;
}

Questo se ne è occupato. Spero che questo possa aiutare anche gli altri.


Probabilmente devi anche saltare i tocchi sul campo di testo probabilmente.
Steven Kramer,

5
Ho avuto più fortuna conreturn ![touch.view isKindOfClass:[UITableViewCell class]];
Josh Paradroid il

1
@Josh Questo non funziona se tocchi le etichette all'interno della cella. In realtà la risposta di Jason funziona perfettamente per ciò di cui ho bisogno.
William T.

Ma questa non è la risposta al tuo problema, no? Questo è un / o. Fondamentalmente qualsiasi tocco sulla vista della tabella è insignificante con il riconoscimento dei gesti .... quello che vuoi è SIA il riconoscimento dei gesti che la selezione della vista della tabella per funzionare
PostCodeism

5
@Durgaprasad Per disabilitare esplicitamente il riconoscimento dei gesti quando si selezionano le celle ho aggiunto una ![touch.view isEqual: self.tableView] &&prima [touch.view isDescendantOfView: self.tableView]per escludere tableViewse stessa (perché isDescendantOfView:restituisce anche YESse la vista dell'argomento è la vista del ricevitore stessa). Spero che aiuti altre persone che vogliono lo stesso risultato.
anneblue,

220

Il modo più semplice per risolvere questo problema è:

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

Ciò consente di UIGestureRecognizerriconoscere il tocco e passare anche il tocco al risponditore successivo. Una conseguenza non intenzionale di questo metodo è se si dispone di uno UITableViewCellschermo che spinge un altro controller di visualizzazione. Se l'utente tocca la riga per chiudere la tastiera, verranno riconosciuti sia la tastiera che il push. Dubito che questo sia ciò che intendi, ma questo metodo è adeguato per molte situazioni.

Inoltre, espandendo la risposta di Robert, se si dispone di un puntatore alla vista tabella in questione, è possibile confrontare direttamente la sua classe invece di convertirsi in una stringa e sperare che Apple non cambi la nomenclatura:

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

    return //YES/NO

}

Ricorda, devi anche dichiarare UIGestureRecognizerdi avere un delegato con questo codice al suo interno.


4
Grazie, setCancelsTouchesInView cambia tutto :)
PostCodeism

Potrebbe comunque essere utile utilizzare isDescendantOfViewinvece di verificare se classè uguale, poiché è possibile toccare una vista secondaria. Dipende da ciò di cui hai bisogno.
shim

71

Set cancelsTouchesInViewdel tuo riconoscimento su false. Altrimenti, "consuma" il tocco per se stesso e non lo passa alla vista tabella. Ecco perché l'evento di selezione non si verifica mai.

per esempio in swift

let tapOnScreen: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "CheckTheTime")
tapOnScreen.cancelsTouchesInView = false
view.addGestureRecognizer(tapOnScreen)

1
Grazie mille per questo!
Bishan,

1
Questo è il più pulito di Swift. Grazie
Dx_

funziona bene all'interno delle celle in vista tabella, adorabile e semplice!
Abdoelrhman,

@laoyur very true
bLacK hoLE

ho ancora il clic del cellulare invece del gesto del tocco
famfamfam

42

E per Swift (basato sulla risposta di @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
}

Grazie mille questo mi ha risparmiato tempo.
Bishan,

22

Potrei avere una soluzione migliore per aggiungere un gesto di tocco su una vista tabella ma consentendo la selezione della cella allo stesso tempo:

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

Cerco solo una cella nel punto dello schermo in cui l'utente sta toccando. Se non viene trovato alcun percorso dell'indice, lascio che il gesto riceva il tocco, altrimenti lo annullo. Per me funziona benissimo.


1
Questa soluzione oscilla! Molto utile per distinguere la cella dal campo vuoto della tabella ..
Alessandro Ornano,

18

Penso che non sia necessario scrivere blocchi di codici semplicemente impostati cancelsTouchesInViewsu falseper il tuo oggetto gesto, per impostazione predefinita è truee devi solo impostarlo false. Se stai usando un UITapGestureoggetto nel tuo codice e anche usando UIScrollView(tableview, collectionview), imposta questa proprietàfalse

let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)

Grazie per aver inviato questa risposta
Gustavo Baiocchi Costa,

14

Una soluzione simile consiste nell'implementare gestureRecognizer:shouldReceiveTouch:utilizzando la classe della vista per determinare quale azione intraprendere. Questo approccio ha il vantaggio di non mascherare i tocchi nella regione che circonda direttamente la tabella (le viste di queste aree discendono ancora dalle istanze di UITableView, ma non rappresentano celle).

Questo ha anche un vantaggio che funziona con più tabelle su una singola vista (senza aggiungere codice aggiuntivo).

Avvertenza: si presume che Apple non cambi il nome della classe.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    return ![NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"];
}

1
Per eliminare la stringa del nome della classe, utilizzare questa rigareturn ![[touch.view.superview class] isSubclassOfClass:[UITableViewCell class]];
Nianliang,

7

Per Swift 4.2 la soluzione era implementare UIGestureRecognizerDelegatee aggiungere quanto segue:

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

quando si fa clic sulla vista tabella, questo metodo delegato restituisce false e il didSelectRowAtIndexPathmetodo della vista tabella funziona correttamente.


1
grazie amico per aver pubblicato questa risposta, mi fai risparmiare tempo.
Jay Bhalani,

4

Ho avuto una situazione diversa in cui volevo solo chiamare la funzione del gesto del tocco quando l'utente toccava al di fuori della vista tabella. Se l'utente ha toccato all'interno della vista tabella, la funzione di gesto touch non deve essere chiamata. Inoltre, se viene chiamata la funzione gesto touch, dovrebbe comunque passare l'evento touch alla vista toccata anziché consumarla.

Il codice risultante è una combinazione della risposta di Abdulrahman Masoud e della risposta di 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")
    }
}

E nella MyViewControllerclasse, la classe che ha UITableView, nella onViewDidLoad(), mi sono assicurato di chiamare addGestureRecognizer():

class MyViewController: UIViewController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        self.addGestureRecognizer()
        ...
    }
    ...
}

3

Basta impostare cancels touches in viewa falsequalsiasi gesto di sotto della(table/collection)View :

- Interfacebuilder

InterfaceBuilder

- Codice

<#gestureRecognizer#>.cancelsTouchesInView = false

cosa succede se desidero disabilitare il suo funzionamento non solo consentendo a didSelectRowAt di lavorare con esso?
Eyad Shokry,

1

Mentre è tardi e molte persone scoprono che i suggerimenti di cui sopra funzionano bene, non sono riuscito a far funzionare i metodi di Jason o TMilligan.

Ho una tabella statica Visualizza con più celle contenenti campi di testo che ricevono input numerici utilizzando solo la tastiera numerica. Questo era l'ideale per me:

- (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;
}

Assicurati di averlo implementato <UIGestureRecognizerDelegate>nel tuo file .h.

Questa riga ![touch.view isKindOfClass:[UITableViewCell class]]controlla se è stato toccato un tableViewCell e ignora qualsiasi tastiera attiva.


1

Una soluzione semplice sta usando le istanze UIControl in UITableViewCell per ottenere tocchi. Puoi aggiungere qualsiasi vista con userInteractionEnables == NO a UIControl per ottenere i tocchi.


0

Ecco la mia soluzione, che lega il riconoscitore dovrebbeRicevereTouch direttamente alla visualizzazione della tastiera.

Nel delegato del riconoscimento dei gesti dei tocchi:

#pragma mark - UIGestureRecognizerDelegate

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

    return NO;
}

E il PFXKeyboardStateListener.h:

@interface PFXKeyboardStateListener : NSObject
{
    BOOL _isVisible;
}

+ (PFXKeyboardStateListener *)sharedInstance;

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

@end

E il 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

Potresti voler aggiornare il modello singleton dell'ascoltatore di tastiera, non ci sono ancora arrivato. Spero che questo funzioni per tutti gli altri così come funziona per me. ^^


0

Implementare questo metodo per delegato di 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;
}

0

In breve tempo puoi usarlo all'interno

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

func CheckTheTime() -> Bool{
    return true
}

0

Il mio caso è stato diverso, tra cui una barra di ricerca e la visualizzazione di Uitable su self.view. Volevo chiudere la tastiera della barra di ricerca toccando la vista.

var tapGestureRecognizer:UITapGestureRecognizer?

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

Sui metodi delegati UISearchBar:

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

Quando l'utente tocca self.view:

func handleTap(recognizer: UITapGestureRecognizer) {
    sampleSearchBar.endEditing(true)
    sampleSearchBar.resignFirstResponder()
}

0

Swift 5, maggio 2020.

Ho un textField e un tableView che diventa visibile quando inserisco il testo.

Stato iniziale Quindi voglio 2 eventi diversi quando tocco tableViewCell o qualcos'altro.

Tastiera e tableView vengono visualizzati

Per prima cosa aggiungiamo tapGestureRecognizer.

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

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

Quindi aggiungiamo il seguente controllo in 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
    }
}

}

Se voglio prima nascondere la tastiera, tableView rimane visibile e rispondente ai miei tocchi.

inserisci qui la descrizione dell'immagine


-1

Questa è la mia soluzione basata sulle risposte sopra ... Ha funzionato per me ...

//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
}

-1

PROBLEMA: Nel mio caso, il problema era che inizialmente avevo inserito un pulsante in ogni cella collectionView e impostato i vincoli per riempire la cella, in modo che quando si faceva clic sulla cella facesse clic sul pulsante, tuttavia la funzione dei pulsanti era vuota, quindi nulla era sembra accadere.

FIX: ho risolto il problema rimuovendo il pulsante dalla cella della vista raccolta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.