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.