UIGestureRecognizer bloquea la subvista para manejar eventos táctiles

82

Estoy tratando de averiguar cómo se hace esto de la manera correcta . Intenté representar la situación: ingrese la descripción de la imagen aquí

Estoy agregando UITableViewcomo una subvista de un UIView. El UIViewresponde a un toque y pinchGestureRecognizer, pero al hacerlo, la vista de mesa deja de reaccionar a esos dos gestos (todavía reacciona a los deslizamientos).

Lo hice funcionar con el siguiente código, pero obviamente no es una buena solución y estoy seguro de que hay una mejor manera. Esto se pone en la UIView(la supervista):

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if([super hitTest:point withEvent:event] == self) {
        for (id gesture in self.gestureRecognizers) {
            [gesture setEnabled:YES];
        }
        return self;
    }
    for (id gesture in self.gestureRecognizers) {
        [gesture setEnabled:NO];
    }
    return [self.subviews lastObject];
}
andershqst
fuente

Respuestas:

182

Tuve un problema muy similar y encontré mi solución en esta pregunta SO . En resumen, configúrelo como delegado suyo UIGestureRecognizery luego verifique la vista de destino antes de permitir que su reconocedor procese el toque. El método de delegado relevante es:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch
Justin
fuente
3
Me gusta más esta solución, ya que no implica jugar con los toques, hitTest:withEvent:o pointInside:withEvent:.
DarkDust
6
Solución limpia, como puede probar, por ejemplo, return !(touch.view == givenView);si solo desea excluir una vista determinada o return !(touch.view.tag == kTagNumReservedForExcludingViews);cuando desea que el reconocedor deje de procesar el toque en un montón de subvistas diferentes.
cado
6
Haría la prueba de éxito con - (BOOL)isDescendantOfView:(UIView *)view. Esto también funciona bien - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)eventcuando se subclasifica UIGestureRecognizer.
Christoph
1
@Justin, ¿qué pasa si nuestro reconocedor de gestos pertenece a uiscrollview y no podemos delegar los reconocedores de gestos?
Gökhan Barış Aker
109

El bloqueo de eventos táctiles a subvistas es el comportamiento predeterminado. Puede cambiar este comportamiento:

UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(agentPickerTapped:)];
r.cancelsTouchesInView = NO;
[agentPicker addGestureRecognizer:r];
Clive Paterson
fuente
5
Esto enviará los eventos táctiles a las subvistas, pero también se enviará al reconocedor de gestos. Por lo tanto, esto evitará el bloqueo de las subvistas, pero el reconocedor de gestos seguirá siendo reconocido en las subvistas.
Jonathan.
@Jonathan. Sí estoy de acuerdo con usted. Lo que hice en mi método de manejo de gestos fue verificar si la ubicación del gesto ocurre dentro de la subvista en cuestión, en cuyo caso no necesito ejecutar el resto del código. Además, una de las razones por las que elegí esta solución es que UITapGestureRecognizer no declara el translationInViewmétodo. Por lo tanto, implementar el método UIGestureRecognizerDelegate mencionado anteriormente solo provocaría fallas con error ... unrecognized selector sent to blah. Para comprobarlo, usar algo como: CGRectContainsPoint(subview.bounds, [recognizer locationInView:subview]).
MkVal
Según la pregunta de OP, esta respuesta debe seleccionarse como respuesta principal.
farzadshbfn
5

Estaba mostrando una subvista desplegable que tenía su propia vista de tabla. Como resultado, a touch.viewveces devuelve clases como UITableViewCell. Tuve que pasar por la (s) superclase (s) para asegurarme de que era la subclase que pensé que era:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    UIView *view = touch.view;
    while (view.class != UIView.class) {
        // Check if superclass is of type dropdown
        if (view.class == dropDown.class) { // dropDown is an ivar; replace with your own
            NSLog(@"Is of type dropdown; returning NO");
            return NO;
        } else {
            view = view.superview;
        }
    }

    return YES;
}
Joslinm
fuente
El ciclo while explica eso
joslinm
5

Sobre la base de la respuesta de @Pin Shih Wang . Ignoramos todos los toques que no sean los de la vista que contiene el reconocedor de gestos de toque. Todos los toques se reenvían a la jerarquía de vistas con la normalidad que establecimos tapGestureRecognizer.cancelsTouchesInView = false. Aquí está el código en Swift3 / 4:

func ensureBackgroundTapDismissesKeyboard() {
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    tapGestureRecognizer.cancelsTouchesInView = false
    self.view.addGestureRecognizer(tapGestureRecognizer)
}

@objc func handleTap(recognizer: UIGestureRecognizer) {
    let location = recognizer.location(in: self.view)
    let hitTestView = self.view.hitTest(location, with: UIEvent())
    if hitTestView?.gestureRecognizers?.contains(recognizer) == .some(true) {
        // I dismiss the keyboard on a tap on the scroll view
        // REPLACE with own logic
        self.view.endEditing(true)
    }
}
Nick Ager
fuente
¿Cómo puedo comparar el reconocedor de gestos con un UISwipeActionStandardButton? Probé con.some(UISwipeActionStandardButton)
Jalil
4

Una posibilidad es subclasificar su reconocedor de gestos (si aún no lo ha hecho) y anularlo de -touchesBegan:withEvent:manera que determine si cada toque comenzó en una subvista excluida y solicita -ignoreTouch:forEvent:ese toque si lo hizo.

Obviamente, también deberá agregar una propiedad para realizar un seguimiento de la subvista excluida, o quizás mejor, una matriz de subvistas excluidas.

Caleb
fuente
Hay un montón de código aquí github.com/…
johndpope
2

Es posible prescindir de heredar ninguna clase.

puedes comprobar los reconocimiento de gestos en el selector de devolución de llamada de gestos

si view.gestureRecognizers no contiene su gestoRecognizer, simplemente ignórelo

por ejemplo

- (void)viewDidLoad
{
    UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self     action:@selector(handleSingleTap:)];
    singleTapGesture.numberOfTapsRequired = 1;
}

ver view.gestureRecognizers aquí

- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer
{
    UIEvent *event = [[UIEvent alloc] init];
    CGPoint location = [gestureRecognizer locationInView:self.view];

    //check actually view you hit via hitTest
    UIView *view = [self.view hitTest:location withEvent:event];

    if ([view.gestureRecognizers containsObject:gestureRecognizer]) {
        //your UIView
        //do something
    }
    else {
        //your UITableView or some thing else...
        //ignore
    }
}
Pin Shih Wang
fuente
Como se mencionó en otras respuestas, si usa este método, asegúrese de que el reconocedor de gestos de toque reenvíe el toque a su jerarquía de vista con: singleTapGesture.cancelsTouchesInView = NO;agregado viewDidLoadarriba
Nick Ager
1

Creé una subclase UIGestureRecognizer diseñada para bloquear todos los reconocedores de gestos adjuntos a una supervista de una vista específica.

Es parte de mi proyecto WEPopover. Puedes encontrarlo aquí .

Werner Altewischer
fuente
1

implemente un delegado para todos los reconocedores de parentView y coloque el método GestureRecognizer en el delegado que es responsable de la activación simultánea de reconocedores:

func gestureRecognizer(UIGestureRecognizer,       shouldBeRequiredToFailByGestureRecognizer:UIGestureRecognizer) -> Bool {
if (otherGestureRecognizer.view.isDescendantOfView(gestureRecognizer.view)) {
    return true
    } else {
    return false
}

}

Puede usar los métodos de falla si desea que los niños se activen pero no los reconocedores de padres:

https://developer.apple.com/reference/uikit/uigesturerecognizerdelegate

usuario7270881
fuente
0

También estaba haciendo un popover y así es como lo hice.

func didTap(sender: UITapGestureRecognizer) {

    let tapLocation = sender.locationInView(tableView)

    if let _ = tableView.indexPathForRowAtPoint(tapLocation) {
        sender.cancelsTouchesInView = false
    }
    else {
        delegate?.menuDimissed()
    }
}
Maria
fuente
0

Puede apagarlo y encenderlo ... en mi código hice algo como esto ya que necesitaba apagarlo cuando el teclado no se mostraba, puede aplicarlo a su situación:

llamar a esto es viewdidload etc:

NSNotificationCenter    *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(notifyShowKeyboard:) name:UIKeyboardDidShowNotification object:nil];
[center addObserver:self selector:@selector(notifyHideKeyboard:) name:UIKeyboardWillHideNotification object:nil];

luego crea los dos métodos:

-(void) notifyShowKeyboard:(NSNotification *)inNotification 
{
    tap.enabled=true;  // turn the gesture on
}

-(void) notifyHideKeyboard:(NSNotification *)inNotification 
{
    tap.enabled=false;  //turn the gesture off so it wont consume the touch event
}

Lo que hace es deshabilitar el grifo. Sin embargo, tuve que convertir el tap en una variable de instancia y liberarlo en dealloc.

j2emanue
fuente