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];
Matrosov Alexander
fonte
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
}
Ravi
fonte
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
}
Ian
fonte
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.

Antoine
fonte
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.

Imanou Petit
fonte
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
        }
    }
}
Arru
fonte
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
  }
}
CoachThys
fonte
4

Caminho rápido e claro

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == self.view
}
Musa almatri
fonte
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
}
Rico
fonte
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

MrDEV
fonte
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
     }
Shay Moreno
fonte
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
}
Jonathan Cabrera
fonte
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)
}
PDK
fonte