UITapGestureRecognizer toque em self.view, mas ignore as subvisualizações


93

Preciso implementar um recurso que invocará algum código quando eu tocar duas vezes em self.view (visualização de UIViewCotroller). Mas o problema é que tenho outro objeto de IU nesta visualização e não quero anexar nenhum objeto reconhecedor a todos eles. Encontrei esse método abaixo de como fazer gestos na minha vista e sei como funciona. No momento, estou diante do handicap de qual forma devo escolher para criar este reconhecedor ignorando a subvisão. Alguma ideia? Obrigado.

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[self.view addGestureRecognizer:doubleTap];

1
Não tenho certeza, mas você já tentou definir cancelsTouchesInView como NO no reconhecedor? so [doubleTap setCancelsTouchesInView: NO];
JDx

Respostas:


142

Você deve adotar o UIGestureRecognizerDelegateprotocolo dentro do selfobjeto e chamar o método abaixo para verificar a visualização. Dentro deste método, compare sua visão touch.viewe retorne o bool apropriado (Sim / Não). Algo assim:

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

Edit: Por favor, verifique também a resposta de @ Ian!

Swift 5

// MARK: UIGestureRecognizerDelegate methods, You need to set the delegate of the recognizer
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
     if touch.view?.isDescendant(of: tableView) == true {
        return false
     }
     return true
}

mais uma pergunta Tenho alguns botões na minha visão que precisam funcionar. como posso definir alguma prioridade para eles?
Matrosov Alexander

se os seus botões tiverem seus próprios reconhecedores de gestos (provavelmente), cave em seus interiores e agarre-os e leia a documentação, -[UIGestureRecognizer requireGestureRecognizerToFail:]mas pode funcionar sem mod'-los
bshirley

estava procurando por isso por horas! Obrigado! ;)
MasterRazer

Se seu ifteste falhar, sua implementação falhará em retornar um BOOL; para o estilo apropriado, retorne YES após um bloco if (usando { }) ou em um elsebranch. Obrigado, porém, me poupou um pouco de leitura.
RobP de

2
Uma visualização é um descendente de si mesma, então isso não funciona ... (a abordagem geral está ok, veja outra resposta)
Ixx

107

Outra abordagem é apenas comparar se a visualização do toque é a visualização de gestos, pois os descendentes não passarão na condição. Uma linha simples e agradável:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == gestureRecognizer.view
}

2
Isso está funcionando melhor para mim do que a resposta aceita. Muito mais conciso.
Suman Roy

1
@Ian este método não é invocado ao tocar na visualização.
Praburaj

1
Ótima resposta concisa!
scurioni

A resposta aceita nem funcionou, mas funcionou perfeitamente para mim.
Shalin Shah

@Prabu provavelmente porque o delegado do reconhecedor de gestos deve ser definido antes
Kubba

23

E para a variante Swift:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if touch.view.isDescendantOfView(yourSubView){
        return false
    }
    return true
}

É bom saber que isDescendantOfViewretorna um Booleanvalor indicando se o receptor é uma subvisão de uma determinada visão ou idêntico a essa visão.


1
Não se esqueça de definir o UIGestureRecognizerDelegate em sua classe!
Fox5150

4
Em vez de fazer essa instrução if, você não pode simplesmente retornar isso? return! touch.view.isDescendant (de: gestRecognizer.view) Além disso, nova sintaxe em swift 3 ^^
EdwardSanchez

12

Com o Swift 5 e iOS 12, UIGestureRecognizerDelegatetem um método chamado gestureRecognizer(_:shouldReceive:). gestureRecognizer(_:shouldReceive:)tem a seguinte declaração:

Pergunte ao delegado se um reconhecedor de gestos deve receber um objeto que representa um toque.

optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool

O código completo abaixo mostra uma possível implementação para gestureRecognizer(_:shouldReceive:). Com este código, um toque em uma subvisão da ViewControllervisão do (incluindo imageView) não acionará o printHello(_:)método.

import UIKit

class ViewController: UIViewController, UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printHello))
        tapGestureRecognizer.delegate = self
        view.addGestureRecognizer(tapGestureRecognizer)

        let imageView = UIImageView(image: UIImage(named: "icon")!)
        imageView.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
        view.addSubview(imageView)

        // ⚠️ Enable user interaction for imageView so that it can participate to touch events.
        // Otherwise, taps on imageView will be forwarded to its superview and managed by it.
        imageView.isUserInteractionEnabled = true
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        // Prevent subviews of a specific view to send touch events to the view's gesture recognizers.
        if let touchedView = touch.view, let gestureView = gestureRecognizer.view, touchedView.isDescendant(of: gestureView), touchedView !== gestureView {
            return false
        }
        return true
    }

    @objc func printHello(_ sender: UITapGestureRecognizer) {
        print("Hello")
    }

}

Uma implementação alternativa de gestureRecognizer(_:shouldReceive:)pode ser:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return gestureRecognizer.view === touch.view
}

Observe, entretanto, que este código alternativo não verifica se touch.viewé uma subvisão de gestureRecognizer.view.


10

Solução rápida completa (delegado deve ser implementado E definido para reconhecedor (es)):

class MyViewController: UIViewController UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(onBaseTapOnly))
        doubleTapRecognizer.numberOfTapsRequired = 2
        doubleTapRecognizer.delegate = self
        self.view.addGestureRecognizer(doubleTapRecognizer)
    }

    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
        if touch.view.isDescendantOfView(self.view){
            return false
        }
        return true
    }

    func onBaseTapOnly(sender: UITapGestureRecognizer) {
        if sender.state == .Ended {
            //react to tap
        }
    }
}

6

Variante usando CGPoint que você toca (SWIFT 4.0)

class MyViewController: UIViewController, UIGestureRecognizerDelegate {

  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {

// Get the location in CGPoint
    let location = touch.location(in: nil)

// Check if location is inside the view to avoid
    if viewToAvoid.frame.contains(location) {
        return false
    }

    return true
  }
}

4

Caminho rápido e claro

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == self.view
}

Nunca é chamado no iOS 13, pelo menos com swipeGestureRecognizer.
Chewie The Chorkie

Como isso é diferente da resposta de Ian?
sweetfa

3

Observe que a API GestRecognizer mudou para:

gestoRecognizer (_: shouldReceive :)

Observe especialmente o indicador de sublinhado (pular) para o rótulo externo do primeiro parâmetro.

Usando muitos dos exemplos fornecidos acima, eu não estava recebendo o evento. Abaixo está um exemplo que funciona para as versões atuais do Swift (3+).

public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    var shouldReceive = false
    if let clickedView = touch.view {
        if clickedView == self.view {
            shouldReceive = true;
        }
    }
    return shouldReceive
}

2

Além das soluções acima, não se esqueça de verificar User Interaction Enabledsua visão secundária.

insira a descrição da imagem aqui


2

Tive que evitar o gesto na visão infantil. A única coisa que funcionou foi permitir e manter a primeira visualização e evitar o gesto em todas as próximas visualizações:

   var gestureView: UIView? = nil

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if (gestureView == nil || gestureView == touch.view){
            gestureView = touch.view
            return true
        }
        return false
     }

1

Swift 4:

touch.view agora é opcional, portanto, com base na resposta de @Antoine:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let touchedView = touch.view, touchedView.isDescendant(of: deductibleBackgroundView) {
        return false
    }
    return true
}

0

Se você não quiser que seu 'identificador de toque duplo' entre em conflito com seus botões e / ou outros controles, você pode definir selfcomo UIGestureRecognizerDelegatee implementar:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
{
    return !(touch.view is UIControl)
}
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.