UITapGestureRecognizer rompe UITableView didSelectRowAtIndexPath

207

He escrito mi propia función para desplazar los campos de texto hacia arriba cuando aparece el teclado. Para descartar el teclado tocando el campo de texto, he creado uno UITapGestureRecognizerque se encarga de renunciar al primer respondedor en el campo de texto al tocar.

Ahora también he creado un autocompletado para el campo de texto que crea UITableViewjusto debajo del campo de texto y lo llena con elementos a medida que el usuario ingresa el texto.

Sin embargo, al seleccionar una de las entradas en la tabla de autocompletado, didSelectRowAtIndexPathno se llama. En cambio, parece que se está llamando al reconocedor de gestos de toque y simplemente renuncia al primer respondedor.

Supongo que hay alguna forma de decirle al reconocedor de gestos de toque que continúe pasando el mensaje de toque hacia abajo UITableView, pero no puedo entender qué es. Cualquier ayuda será muy apreciada.

Jason
fuente
Ver este hilo .
Duncan Babbage

Respuestas:

258

Ok, finalmente lo encontré después de buscar a través de documentos de reconocimiento de gestos.

La solución fue implementar UIGestureRecognizerDelegatey agregar lo siguiente:

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

Eso lo cuidó. Esperemos que esto ayude a otros también.

Jason
fuente
Probablemente también deba omitir toques en el campo de texto.
Steven Kramer
55
Tuve más suerte conreturn ![touch.view isKindOfClass:[UITableViewCell class]];
Josh Paradroid
1
@ Josh Esto no funciona si toca las etiquetas dentro de la celda. En realidad, la respuesta de Jason funciona perfecta para lo que necesito.
William T.
Pero esta no es la respuesta a tu problema, ¿no? Este es un o / o. Básicamente cualquier contacto en la vista de tabla es insignificante con el reconocedor gesto .... lo que quiere es a la vez el reconocedor gesto y la mesa de vista de selección de trabajos
PostCodeism
55
@Durgaprasad Para deshabilitar el reconocedor de gestos explícitamente al seleccionar las celdas, agregué un ![touch.view isEqual: self.tableView] &&antes del [touch.view isDescendantOfView: self.tableView]para excluirlo tableView(porque isDescendantOfView:también se devuelve YESsi la vista de argumento es la vista del receptor en sí). Espero que ayude a otras personas que quieren el mismo resultado.
anneblue
220

La forma más fácil de resolver este problema es:

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

Esto permite UIGestureRecognizerreconocer el toque y también pasar el toque al siguiente respondedor. Una consecuencia no deseada de este método es si tiene una UITableViewCellpantalla que empuja a otro controlador de vista. Si el usuario toca la fila para descartar el teclado, se reconocerán tanto el teclado como la pulsación. Dudo que esto sea lo que pretendes, pero este método es adecuado para muchas situaciones.

Además, expandiendo la respuesta de Robert, si tiene un puntero a la vista de tabla en cuestión, puede comparar directamente su clase en lugar de tener que convertirla en una cadena y esperar que Apple no cambie la nomenclatura:

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

    return //YES/NO

}

Recuerde, también debe declarar UIGestureRecognizerque tiene un delegado con este código.

TMilligan
fuente
44
Gracias, setCancelsTouchesInView cambia todo :)
PostCodeism
Puede que aún quiera usar isDescendantOfView lugar de simplemente verificar si classes igual, ya que puede estar tocando en una subvista. Depende de lo que necesites.
cuña
71

Conjunto cancelsTouchesInViewde su reconocedor a falso. De lo contrario, "consume" el toque por sí mismo y no lo pasa a la vista de tabla. Es por eso que el evento de selección nunca sucede.

por ejemplo en swift

let tapOnScreen: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "CheckTheTime")
tapOnScreen.cancelsTouchesInView = false
view.addGestureRecognizer(tapOnScreen)
Abdulrahman Masoud
fuente
1
Muchas gracias por esto!
Bishan
1
Este es el más limpio en Swift. Gracias
Dx_
funciona bien dentro de las celdas en tableview, ¡encantador y simple!
Abdoelrhman
@laoyur muy cierto
bLacK hoLE
Todavía tengo clic en el celular en lugar del gesto de toque
famfamfam
42

Y para Swift (basado en la respuesta 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
fuente
Muchas gracias, esto me ahorró tiempo.
Bishan
22

Es posible que tenga una mejor solución para agregar un gesto de toque sobre una vista de tabla, pero al mismo tiempo permitir la selección de celdas:

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

Solo busco una celda en el punto de la pantalla donde el usuario está tocando. Si no se encuentra ninguna ruta de índice, entonces dejo que el gesto reciba el toque; de ​​lo contrario, lo cancelo. Para mí funciona muy bien.

bonokita
fuente
1
¡Esta solución es genial! Muy útil para distinguir la celda del campo vacío de la tabla ..
Alessandro Ornano
18

Creo que no hay necesidad de bloques de escritura de códigos simplemente fijados cancelsTouchesInViewa falsepara su objeto gesto, por defecto es truey sólo hay que configurarlo false. Si está usando un UITapGestureobjeto en su código y también está usando UIScrollView(tableview, collectionview), establezca esta propiedadfalse

let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
SHUBHAYAN KUNDU
fuente
Gracias por publicar esta respuesta
Gustavo Baiocchi Costa
14

Una solución similar es implementar gestureRecognizer:shouldReceiveTouch:usando la clase de vista para determinar qué acción tomar. Este enfoque tiene la ventaja de no enmascarar los grifos en la región que rodea directamente la tabla (las vistas de estas áreas aún descienden de las instancias de UITableView, pero no representan celdas).

Esto también tiene la ventaja de que funciona con varias tablas en una sola vista (sin agregar código adicional).

Advertencia: se supone que Apple no cambiará el nombre de la clase.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    return ![NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"];
}
Robert Altman
fuente
1
Para eliminar la cadena del nombre de la clase, use esta líneareturn ![[touch.view.superview class] isSubclassOfClass:[UITableViewCell class]];
Nianliang
7

Para Swift 4.2 la solución fue implementar UIGestureRecognizerDelegatey agregar lo siguiente:

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

Cuando hace clic en la vista de tabla, este método de delegado devuelve falso y el didSelectRowAtIndexPathmétodo de vista de tabla funciona correctamente.

Ashish Kanani
fuente
1
Gracias amigo por publicar esta respuesta, me ahorras tiempo.
Jay Bhalani
4

Tuve una situación diferente en la que quería que se llamara a la función de gesto táctil solo cuando el usuario tocaba afuera de la vista de tabla. Si el usuario tocó dentro de la vista de tabla, entonces no debería llamarse a la función de gesto táctil. Además, si se llama a la función de gesto táctil, aún debe pasar el evento táctil a la vista que se tocó en lugar de consumirlo.

El código resultante es una combinación de la respuesta de Abdulrahman Masoud y la respuesta 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")
    }
}

Y en la MyViewControllerclase, la clase que tiene UITableView, en el onViewDidLoad(), me aseguré de llamar addGestureRecognizer():

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

Sólo hay que establecer cancels touches in viewque falsepara cualquier gesto por debajo de la (table/collection)View:

- Interfacebuilder

Interfacebuilder

- Código

<#gestureRecognizer#>.cancelsTouchesInView = false
Mojtaba Hosseini
fuente
¿Qué pasa si quiero deshabilitar su trabajo no solo permitiendo que didSelectRowAt funcione con él?
Eyad Shokry
1

Si bien es tarde y muchas personas encuentran que las sugerencias anteriores funcionan bien, no pude lograr que los métodos de Jason o TMilligan funcionen.

Tengo un TableView estático con múltiples celdas que contienen campos de texto que reciben entradas de números usando solo el teclado numérico. Esto fue ideal para mí:

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

Asegúrese de haber implementado esto <UIGestureRecognizerDelegate>en su archivo .h.

Esta línea ![touch.view isKindOfClass:[UITableViewCell class]]verifica si se tocó una tableViewCell y descarta cualquier teclado activo.

App Dev Guy
fuente
1

La solución simple es usar instancias UIControl en UITableViewCell para obtener toques. Puede agregar cualquier vista con userInteractionEnables == NO a UIControl para obtener toques.

Andrew Romanov
fuente
0

Aquí está mi solución, que vincula directamente el reconocedor shouldReceiveTouch a si se muestra el teclado.

En su delegado de reconocimiento de gestos táctiles:

#pragma mark - UIGestureRecognizerDelegate

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

    return NO;
}

Y el PFXKeyboardStateListener.h:

@interface PFXKeyboardStateListener : NSObject
{
    BOOL _isVisible;
}

+ (PFXKeyboardStateListener *)sharedInstance;

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

@end

Y el 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

Es posible que desee actualizar el patrón singleton del oyente del teclado, todavía no lo he logrado. Espero que esto funcione para todos los demás, así como para mí. ^^

bgfriend0
fuente
0

Implemente este método para delegar de 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
fuente
0

En Swift puedes usar esto adentro

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

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

Mi caso fue diferente, incluyendo una barra de búsqueda y una vista de edición en self.view. Quería cerrar el teclado de uisearchbar tocando la vista.

var tapGestureRecognizer:UITapGestureRecognizer?

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

En los métodos de delegado UISearchBar:

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

Cuando el usuario toca self.view:

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

Swift 5, mayo de 2020.

Tengo un textField y un tableView que se hace visible cuando ingreso texto.

Estado inicial Así que quiero 2 eventos diferentes cuando toco tableViewCell o algo más.

Se muestran el teclado y la vista de tabla

Primero agregamos tapGestureRecognizer.

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

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

Luego agregamos la siguiente verificación en 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 quiero ocultar el teclado primero, tableView permanece visible y responde a mis toques.

ingrese la descripción de la imagen aquí

Hombre de pavo
fuente
-1

Esta es mi solución basada en las respuestas anteriores ... Me ha funcionado ...

//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
fuente
-1

PROBLEMA: en mi caso, el problema era que originalmente coloqué un botón en cada celda de collectionView y establecí las restricciones para llenar la celda, de modo que cuando se hacía clic en la celda, se hacía clic en el botón, sin embargo, la función de los botones estaba vacía, por lo que no había nada parece estar sucediendo

REVISIÓN: arreglé esto eliminando el botón de la celda de vista de colección.

Marcus Levi
fuente