UITapGestureRecognizer toca self.view pero ignora las subvistas

95

Necesito implementar una función que invocará algún código cuando toque dos veces en self.view (vista de UIViewCotroller). Pero el problema de que tengo otro objeto de IU en esta vista y no quiero adjuntar ningún objeto de reconocimiento a todos ellos. Encontré este método a continuación sobre cómo hacer gestos en mi vista y sé cómo funciona. En este momento estoy frente a la desventaja de qué manera elegir para crear este reconocedor ignorando la subvista. ¿Algunas ideas? Gracias.

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[self.view addGestureRecognizer:doubleTap];
Alejandro Matrosov
fuente
1
No estoy seguro, pero ¿ha intentado configurar cancelsTouchesInView en NO en el reconocedor? entonces [doubleTap setCancelsTouchesInView: NO];
JDx

Respuestas:

144

Debe adoptar el UIGestureRecognizerDelegateprotocolo dentro del selfobjeto y llamar al método siguiente para verificar la vista. Dentro de este método, verifique su vista touch.viewy devuelva el bool apropiado (Sí / No). Algo como esto:

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

Editar: ¡Por favor, también revisa la respuesta de @ Ian!

Rápido 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
fuente
una pregunta más Tengo algunos botones en mi vista que tienen que funcionar. ¿Cómo puedo establecer alguna prioridad para ellos?
Matrosov Alexander
si sus botones tienen sus propios reconocedores de gestos (probablemente), -[UIGestureRecognizer requireGestureRecognizerToFail:]busque en sus
partes
¡Estuve buscando esto durante horas! ¡Gracias! ;)
MasterRazer
Si su ifprueba falla, su implementación no devuelve un BOOL; para un estilo adecuado, devuelva YES después de un bloque if (usando { }) o en una elserama. Gracias, sin embargo, me salvó un poco de lectura.
RobP
2
Una vista es descendiente de sí misma, por lo que esto no funciona ... (aunque el enfoque general está bien, vea otra respuesta)
Ixx
107

Otro enfoque es simplemente comparar si la vista del tacto es la vista de gestos, porque los descendientes no pasarán la condición. Una bonita y simple frase:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == gestureRecognizer.view
}
Ian
fuente
2
Esto funciona mejor para mí que la respuesta aceptada. Mucho más conciso.
Suman Roy
1
@Ian, este método no se invoca al tocar la vista.
Praburaj
1
¡Gran respuesta concisa!
scurioni
La respuesta aceptada ni siquiera funcionó, pero esto funcionó perfectamente para mí.
Shalin Shah
@Prabu probablemente porque el delegado del reconocedor de gestos debe configurarse antes
Kubba
23

Y para la variante Swift:

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

Es bueno saberlo, isDescendantOfViewdevuelve un Booleanvalor que indica si el receptor es una subvista de una vista determinada o es idéntico a esa vista.

Antoine
fuente
1
¡No olvide configurar UIGestureRecognizerDelegate en su clase!
Fox5150
4
En lugar de hacer esa declaración if, ¿no puedes simplemente devolver esto? return! touch.view.isDescendant (of: GestoRecognizer.view) Además, nueva sintaxis en
Swift
12

Con Swift 5 y iOS 12, UIGestureRecognizerDelegatetiene un método llamado gestureRecognizer(_:shouldReceive:). gestureRecognizer(_:shouldReceive:)tiene la siguiente declaración:

Pregunte al delegado si un reconocedor de gestos debería recibir un objeto que represente un toque.

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

El código completo a continuación muestra una posible implementación para gestureRecognizer(_:shouldReceive:). Con este código, un toque en una subvista de la ViewControllervista de '(incluida imageView) no activará el 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")
    }

}

Una implementación alternativa de gestureRecognizer(_:shouldReceive:)puede ser:

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

Sin embargo, touch.viewtenga en cuenta que este código alternativo no comprueba si es una subvista de gestureRecognizer.view.

Imanou Petit
fuente
10

Solución rápida completa (el delegado debe implementarse Y configurarse para reconocedores):

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
fuente
6

Variante con CGPoint que 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
  }
}
EntrenadorThys
fuente
4

Camino rápido y despejado

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == self.view
}
Musa almatri
fuente
Nunca recibe llamadas en iOS 13, al menos con swipeGestureRecognizer.
Chewie The Chorkie
¿En qué se diferencia esto de la respuesta de Ian?
sweetfa
3

Tenga en cuenta que la API de gestosRecognizer ha cambiado a:

gestoRecognizer (_: shouldReceive :)

Preste especial atención al indicador de subrayado (omisión) de la etiqueta externa del primer parámetro.

Utilizando muchos de los ejemplos proporcionados anteriormente, no recibí el evento. A continuación se muestra un ejemplo que funciona para las versiones actuales de 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
fuente
2

Además de las soluciones anteriores, no olvide verificar User Interaction Enabledsu vista secundaria.

ingrese la descripción de la imagen aquí

MrDEV
fuente
2

Tuve que evitar el gesto en la vista infantil. Lo único que funcionó es permitir y mantener la primera vista y evitar gestos en todas las siguientes vistas:

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

Rápido 4:

touch.view ahora es opcional, por lo que se basa en la respuesta 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
fuente
0

Si no quiere que su 'reconocedor doble toque' al conflicto con sus botones y / u otros controles, se puede establecer selfcomo UIGestureRecognizerDelegatey poner en práctica:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
{
    return !(touch.view is UIControl)
}
PDK
fuente