UIButton dentro de una vista que tiene un UITapGestureRecognizer

184

Tengo vista con a UITapGestureRecognizer. Entonces, cuando toco la vista, aparece otra vista encima de esta vista. Esta nueva vista tiene tres botones. Cuando ahora presiono uno de estos botones, no obtengo la acción de los botones, solo obtengo la acción del gesto de tocar. Así que ya no puedo usar estos botones. ¿Qué puedo hacer para que los eventos lleguen a estos botones? Lo extraño es que los botones aún se resaltan.

No puedo simplemente eliminar el UITapGestureRecognizer después de recibir su toque. Porque con ella también se puede eliminar la nueva vista. Significa que quiero un comportamiento como los controles de video de pantalla completa .

V1ru8
fuente

Respuestas:

256

Puede configurar su controlador o vista (lo que crea el reconocedor de gestos) como delegado de UITapGestureRecognizer. Luego, en el delegado puede implementar -gestureRecognizer:shouldReceiveTouch:. En su implementación, puede probar si el toque pertenece a su nueva subvista, y si es así, indique al reconocedor de gestos que lo ignore. Algo como lo siguiente:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    // test if our control subview is on-screen
    if (self.controlSubview.superview != nil) {
        if ([touch.view isDescendantOfView:self.controlSubview]) {
            // we touched our control surface
            return NO; // ignore the touch
        }
    }
    return YES; // handle the touch
}
Lily Ballard
fuente
En mi archivo de encabezado, mi vista implementó UIGestoreRegognizerDelegate, y en mi .mi agregué su código arriba. El toque nunca entra en este método, va directamente a mi controlador. ¿Algunas ideas?
kmehta
1
@kmehta probablemente olvidó configurar la propiedad de delegado UIGestureRecognizer.
Hasta
¿Se puede generalizar esta respuesta para manejar todos los casos en los que hay una IBAction involucrada?
Martin Wickman
@Martin IBAction se compila para voidque no deje ninguna información en el tiempo de ejecución para su detección. Pero lo que puede hacer es subir la jerarquía de vista, probar UIControly regresar NOsi encuentra algún control.
Lily Ballard
Gracias por esto, me ayuda. si su botón está en UIImageView, asegúrese de que userInteractionEnabled de imageView esté configurado en YES.
james075
156

Como seguimiento al seguimiento de Casey a la respuesta de Kevin Ballard:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if ([touch.view isKindOfClass:[UIControl class]]) {
            // we touched a button, slider, or other UIControl
            return NO; // ignore the touch
        }
    return YES; // handle the touch
}

Básicamente, esto hace que funcionen todos los tipos de controles de entrada de usuario, como botones, controles deslizantes, etc.

cdasher
fuente
1
Esa es una solución flexible que puede acompañarse setCancelsTouchesInView = NOpara no activar el gesto de toque en la interacción con los controles. Sin embargo, puede escribirlo mejor:return ![touch.view isKindOfClass:[UIControl class]];
griga13
Solución Swift 4+: devolución (touch.view es UIControl)
nteissler
103

Encontré esta respuesta aquí: enlace

También puedes usar

tapRecognizer.cancelsTouchesInView = NO;

Lo que evita que el reconocedor de grifos sea el único en atrapar todos los grifos

ACTUALIZACIÓN: Michael mencionó el enlace a la documentación que describe esta propiedad: cancelsTouchesInView

ejazz
fuente
8
Esta debería ser la respuesta aceptada de la OMI. Esto permite que cada subvista maneje el grifo por sí solo y evita que la vista que tiene un tabrecognizador adjunto 'toque en alto' el grifo. No es necesario implementar un delegado si todo lo que desea hacer es hacer clic en una vista (para renunciar al primer respondedor / ocultar el teclado en campos de texto, etc.) ... ¡increíble!
EeKay
44
Esta definitivamente debería ser la respuesta aceptada. Esta respuesta me llevó a leer la documentación de Apple correctamente, lo que deja en claro que el reconocedor de gestos evitará que las subvistas obtengan los eventos reconocidos a menos que haga esto.
Michael van der Westhuizen
1
Esto solía funcionar, pero ahora no funciona para mí ... ¿quizás porque tengo un scrollView debajo del botón? Tuve que implementar el delegado como en las respuestas anteriores.
xissburg
77
Después de experimentar un poco, noté que esto solo funcionará si la vista que contiene el reconocedor de gestos es un hermano del UIControl, y no lo hará si la vista es el padre del UIControl.
xissburg
66
Realmente no funciona según lo previsto ... SÍ, es evitar que el reconocedor de grifos se coma el evento en UIControl. Pero todavía se ejecuta la acción de reconocimiento, que es la acción de ejecución 2 al mismo tiempo.
Tek Yin
71

Como seguimiento a la respuesta de Kevin Ballard, tuve el mismo problema y terminé usando este código:

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

Tiene el mismo efecto, pero esto funcionará en cualquier UIButton a cualquier profundidad de vista (mi UIButton tenía varias vistas de profundidad y el delegado de UIGestureRecognizer no tenía una referencia).

Casey
fuente
66
No quiero ser un idiota aquí, pero es realmente SÍ y NO. Utilice las convenciones de cacao. TRUE / FALSE / NULL es para CoreFoundation-Layer.
steipete
Por lo que he leído, SÍ y NO se crearon realmente para facilitar la lectura. la legibilidad es subjetiva. se deriva del método y las convenciones de denominación de propiedades que no todos están demasiado preocupados. si desea un cumplimiento estricto de la convención de nomenclatura, sí, use SÍ y NO para cualquier NSWhatever. sin embargo, diré que si encuesta a los desarrolladores, obtendrá opiniones encontradas sobre cuál de estos es más legible [button setHidden: YES]; -o bien - [conjunto de botones Oculto: VERDADERO];
Pimp Juice McJones
1
Supongo que lo que estoy tratando de decir es que si la premisa de las convenciones de nomenclatura es la legibilidad, creo que es difícil negar que sea subjetiva en algunos casos. si eres purista, entonces no entenderás esa afirmación.
Pimp Juice McJones
2
Puede parecer subjetivo, pero después de un tiempo de programación de Cocoa, realmente entras en el flujo de código más semánticamente preciso ... lo "VERDADERO" parece realmente incorrecto ahora después de años de Objective-C, porque no coincide muy "oculto" bien.
Kendall Helmstetter Gelner
¿Desde cuándo puede / pasa un gesto de toque a un UIButton como un evento de retoque dentro de UITouch (este último es el evento generado al tocar un UIButton)? Parece duplicado e incorrecto crear un reconocedor de gestos táctiles para un botón que tiene 15 eventos táctiles ya asociados con un gesto táctil. Me gustaría ver el código, no adivinanzas.
James Bush
10

En iOS 6.0 y versiones posteriores, las acciones de control predeterminadas evitan la superposición del comportamiento del reconocedor de gestos. Por ejemplo, la acción predeterminada para un botón es un solo toque. Si tiene un único reconocedor de gestos de toque conectado a la vista principal de un botón y el usuario toca el botón, entonces el método de acción del botón recibe el evento táctil en lugar del reconocedor de gestos. Esto se aplica solo al reconocimiento de gestos que se superpone a la acción predeterminada para un control, que incluye: .....

Del documento API de Apple

Jagie
fuente
8

Estas respuestas fueron incompletas. Tuve que leer varias publicaciones sobre cómo usar esta operación booleana.

En su archivo * .h agregue esto

@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>

En su archivo * .m agregue esto

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {

    NSLog(@"went here ...");

    if ([touch.view isKindOfClass:[UIControl class]])
    {
        // we touched a button, slider, or other UIControl
        return NO; // ignore the touch
    }
    return YES; // handle the touch
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.



    //tap gestrure
    UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
    [tapGestRecog setNumberOfTapsRequired:1];
    [self.view addGestureRecognizer:tapGestRecog];


// This line is very important. if You don't add it then your boolean operation will never get called
tapGestRecog.delegate = self;

}


-(IBAction) screenTappedOnce
{
    NSLog(@"screenTappedOnce ...");

}
Sam B
fuente
7

Encontré otra forma de hacerlo desde aquí . Detecta el toque ya sea dentro de cada botón o no.

(1) pointInside: withEvent: (2) locationInView:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
       shouldReceiveTouch:(UITouch *)touch {
    // Don't recognize taps in the buttons
    return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
            ![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
            ![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
}
MindMirror
fuente
Intenté todas las otras soluciones para esto, pero el enfoque -pointInside: withEvent: fue el único que funcionó para mí.
Kenny Wyland
3

Aquí está la versión Swift de la respuesta de Lily Ballard que funcionó para mí:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if (scrollView.superview != nil) {
        if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
    }
    return true
}
John Riselvato
fuente
3

Swift 5

Botón en supervista con tapgesture

 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let _ = touch.view as? UIButton { return false }
    return true
}

En mi caso, implementar hitTest funcionó para mí. Tenía vista de colección con botón

Este método atraviesa la jerarquía de vistas llamando al point(inside:with:)método de cada subvista para determinar qué subvista debería recibir un evento táctil. Si point(inside:with:)devuelve verdadero, la jerarquía de la subvista se recorre de manera similar hasta que se encuentra la vista frontal que contiene el punto especificado.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard isUserInteractionEnabled else { return nil }

    guard !isHidden else { return nil }

    guard alpha >= 0.01 else { return nil }

    guard self.point(inside: point, with: event) else { return nil }

    for eachImageCell in collectionView.visibleCells {
        for eachImageButton in eachImageCell.subviews {
            if let crossButton = eachImageButton as? UIButton {
                if crossButton.point(inside: convert(point, to: crossButton), with: event) {
                    return crossButton
                }
            }
        }
    }
    return super.hitTest(point, with: event)
}
Shruthi Pal
fuente
1

Puede evitar que UITapGestureRecognizer cancele otros eventos (como tocar el botón) configurando el siguiente valor booleano:

    [tapRecognizer setCancelsTouchesInView:NO];
Himanshu Mahajan
fuente
1

Si su escenario es así:

Tiene una vista simple y algunos botones UIB, controles UITextField agregados como subvistas a esa vista. Ahora desea descartar el teclado cuando toca en cualquier otro lugar de la vista, excepto en los controles (subvistas que agregó)

Entonces la solución es:

Agregue el siguiente método a su XYZViewController.m (que tiene su vista)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}
NaveenRaghuveer
fuente
Primero intenté con la respuesta de @cdasher pero en mi caso, incluso al hacer clic en el botón, obtengo touch.view como mi "vista" en ViewController pero no mi control "UIButton". ¿Puede alguien decir por qué la propiedad vista del tacto no devuelve la vista original en la que pasó del grifo
NaveenRaghuveer
1
No funcionará si tiene una vista de desplazamiento o alguna otra vista que reclame los eventos táctiles.
lkraider
1

Optimizando la respuesta de Cdasher, obtienes

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch 
{
    return ![touch.view isKindOfClass:[UIControl class]];
}
Puentes de arcilla
fuente