UITapGestureRecognizer interrompe UITableView didSelectRowAtIndexPath


207

Escrevi minha própria função para rolar os campos de texto quando o teclado é exibido. Para descartar o teclado tocando fora do campo de texto, criei um UITapGestureRecognizerque se encarrega de renunciar à primeira resposta no campo de texto ao tocar fora.

Agora também criei um preenchimento automático para o UITableViewcampo de texto que cria um logo abaixo do campo de texto e o preenche com itens à medida que o usuário digita o texto.

No entanto, ao selecionar uma das entradas na tabela preenchida automaticamente, didSelectRowAtIndexPathnão é chamado. Em vez disso, parece que o reconhecedor de gestos de toque está sendo chamado e apenas renuncia ao atendedor de chamadas.

Acho que há uma maneira de dizer ao reconhecedor de gestos para continuar transmitindo a mensagem UITableView, mas não consigo descobrir o que é. Qualquer ajuda será muito apreciada.


Respostas:


258

Ok, finalmente o encontrei após algumas pesquisas nos documentos do reconhecedor de gestos.

A solução foi implementar UIGestureRecognizerDelegatee adicionar o seguinte:

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

Isso cuidou disso. Espero que isso ajude outras pessoas também.


Você também precisa pular os toques no campo de texto provavelmente.
Steven Kramer

5
Eu tive mais sorte comreturn ![touch.view isKindOfClass:[UITableViewCell class]];
Josh Paradroid 21/02

1
@ Josh Isso não funciona se você tocar nos rótulos dentro da célula. Na verdade, a resposta de Jason funciona perfeitamente para o que eu preciso.
William T.

Mas essa não é a resposta para o seu problema, não? Este é um ou / ou. Basicamente, qualquer toque na exibição de tabela é insignificante com o reconhecedor de gestos .... o que você quer é tanto a visão de seleção gesto de reconhecimento e mesa de trabalho
PostCodeism

5
@Durgaprasad Para desativar o reconhecedor de gestos explicitamente ao selecionar as células, adicionei um ![touch.view isEqual: self.tableView] &&antes do [touch.view isDescendantOfView: self.tableView]para excluir o tableViewpróprio (porque isDescendantOfView:também retorna YESse a exibição de argumento for a própria exibição do receptor). Espero que ajude outras pessoas que desejam o mesmo resultado.
AnneBlue

220

A maneira mais fácil de resolver esse problema é:

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

Isso permite UIGestureRecognizerreconhecer a torneira e também passar o toque para a próxima resposta. Uma consequência não intencional desse método é se você tiver uma UITableViewCelltela que empurra outro controlador de exibição. Se o usuário tocar na linha para descartar o teclado, o teclado e o push serão reconhecidos. Duvido que seja isso que você pretende, mas esse método é adequado para muitas situações.

Além disso, expandindo a resposta de Robert, se você tiver um ponteiro para a tableview em questão, poderá comparar diretamente sua classe em vez de ter que converter para uma string e esperar que a Apple não mude a nomenclatura:

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

    return //YES/NO

}

Lembre-se, você também deve declarar UIGestureRecognizerter um delegado com esse código.


4
Obrigado, setCancelsTouchesInView muda tudo :)
PostCodeism

Talvez você ainda queira usar, em isDescendantOfViewvez de apenas verificar se o valor classé igual, pois você pode estar tocando em uma subvisão. Depende do que você precisa.
shim

71

Conjunto cancelsTouchesInViewdo seu reconhecedor como false. Caso contrário, ele "consome" o toque por si próprio e não o repassa para a visualização da tabela. É por isso que o evento de seleção nunca acontece.

por exemplo em swift

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

1
Thanx muito por isso!
Bishan 07/07

1
Este é o mais limpo em Swift. Obrigado
Dx_ 08/08/16

funciona bem dentro das células no tableview, adorável e simples!
Abdoelrhman

@laoyur muito verdadeiro
Black Hole

ainda tenho celular clique em vez de gesto de tocar
famfamfam

42

E para Swift (com base na resposta 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
}

Muito obrigado, isso economizou meu tempo.
Bishan

22

Talvez eu tenha uma solução melhor para adicionar um gesto de toque em uma exibição de tabela, mas permitindo a seleção de células ao mesmo 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
}

Eu apenas procuro uma célula no ponto da tela em que o usuário está tocando. Se nenhum caminho do índice for encontrado, deixo o gesto receber o toque, caso contrário eu o cancelo. Para mim, funciona muito bem.


1
Esta solução é demais! Muito útil para distinguir células do campo vazio da tabela ..
Alessandro Ornano

18

Eu acho que não há necessidade de blocos de escrita de códigos simplesmente definidas cancelsTouchesInViewpara falseo seu objeto gesto, por padrão ele é truee você apenas tem que configurá-lo false. Se você estiver usando um UITapGestureobjeto no seu código e também usando UIScrollView(tableview, collectionview), defina esta propriedadefalse

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

Obrigado por postar esta resposta
Gustavo Baiocchi Costa

14

Uma solução semelhante é implementar gestureRecognizer:shouldReceiveTouch: usando a classe da exibição para determinar qual ação executar. Essa abordagem tem a vantagem de não mascarar os toques na região diretamente ao redor da tabela (as visualizações dessa área ainda descendem das instâncias do UITableView, mas elas não representam células).

Isso também tem o bônus de funcionar com várias tabelas em uma única exibição (sem adicionar código extra).

Advertência: existe uma suposição de que a Apple não alterará o nome da classe.

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

1
Para eliminar a sequência do nome da classe, use esta linhareturn ![[touch.view.superview class] isSubclassOfClass:[UITableViewCell class]];
Nianliang 26/12/16

7

Para o Swift 4.2, a solução foi implementar UIGestureRecognizerDelegatee adicionar o seguinte:

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

quando você clica na exibição de tabela, esse método delegado retorna falso e o didSelectRowAtIndexPathmétodo da exibição de tabela está funcionando corretamente.


1
obrigado amigo por postar esta resposta, você economiza meu tempo.
Jay Bhalani

4

Eu tive uma situação diferente em que queria que a função de gesto de toque fosse chamada apenas quando o usuário tocou fora da visualização da tabela. Se o usuário tocou na visualização da tabela, a função de gesto de toque não deve ser chamada. Além disso, se a função de gesto de toque for chamada, ela ainda deve passar o evento de toque para a exibição que foi tocada em vez de consumi-lo.

O código resultante é uma combinação da resposta de Abdulrahman Masoud e a resposta 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")
    }
}

E na MyViewControllerclasse, a classe que tem o UITableView, no onViewDidLoad(), fiz questão de chamar addGestureRecognizer():

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

3

Basta definir cancels touches in viewa falsepara qualquer gesto debaixo da (table/collection)View:

- Interfacebuilder

Interfacebuilder

- Código

<#gestureRecognizer#>.cancelsTouchesInView = false

e se eu quiser desativar o trabalho, não apenas permitindo que o didSelectRowAt trabalhe com ele?
Eyad Shokry 8/08/19

1

Embora seja tarde e muitas pessoas achem que as sugestões acima funcionam bem, não consegui que os métodos de Jason ou TMilligan funcionassem.

Eu tenho um tableView estático com várias células contendo textFields que recebem entradas de número usando apenas o teclado numérico. Isso foi ideal para mim:

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

Verifique se você implementou isso <UIGestureRecognizerDelegate>no seu arquivo .h.

Essa linha ![touch.view isKindOfClass:[UITableViewCell class]]verifica se um tableViewCell foi tocado e descarta qualquer teclado ativo.


1

A solução simples é usar instâncias UIControl no UITableViewCell para obter retoques. Você pode adicionar visualizações com userInteractionEnables == NO no UIControl para obter toques.


0

Aqui está minha solução, que vincula o shouldReceiveTouch do reconhecedor diretamente ao fato de o teclado estar aparecendo.

No seu delegado reconhecedor de gestos de toque:

#pragma mark - UIGestureRecognizerDelegate

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

    return NO;
}

E o PFXKeyboardStateListener.h:

@interface PFXKeyboardStateListener : NSObject
{
    BOOL _isVisible;
}

+ (PFXKeyboardStateListener *)sharedInstance;

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

@end

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

Você pode atualizar o padrão singleton do ouvinte de teclado, ainda não cheguei a ele. Espero que isso funcione para todos os outros, assim como para mim. ^^


0

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

No Swift você pode usar isso dentro

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

func CheckTheTime() -> Bool{
    return true
}

0

Meu caso foi diferente, incluindo uma uisearchbar e uitableview no self.view. Eu queria descartar o teclado da uisearchbar tocando na visualização.

var tapGestureRecognizer:UITapGestureRecognizer?

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

Nos métodos de delegação UISearchBar:

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

Quando o usuário toca em self.view:

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

0

Swift 5, maio de 2020.

Eu tenho um textField e um tableView que fica visível quando insiro o texto.

Estado inicial Então, eu quero 2 eventos diferentes quando toco em tableViewCell ou algo mais.

O teclado e o tableView estão sendo mostrados

Primeiro, adicionamos tapGestureRecognizer.

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

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

Em seguida, adicionamos a seguinte verificação no 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 eu quiser ocultar o teclado primeiro, o tableView permanecerá visível e responsivo aos meus toques.

insira a descrição da imagem aqui


-1

Esta é a minha solução com base nas respostas acima ... Funcionou para mim ...

//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: No meu caso, o problema era que eu originalmente coloquei um botão em cada célula collectionView e defini as restrições para preencher a célula, de modo que, quando a célula fosse clicada, ela clicaria no botão, no entanto, a função dos botões estava vazia e nada estava parece estar acontecendo.

CORRECÇÃO: Corrigi isso removendo o botão da célula de exibição de coleção.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.