iOS: reenvíe todos los toques a través de una vista

141

Tengo una vista superpuesta sobre muchas otras vistas. Solo estoy usando el modo general para detectar algunos toques en la pantalla, pero aparte de eso, no quiero que la vista detenga el comportamiento de otras vistas debajo, que son vistas de desplazamiento, etc. ¿Cómo puedo reenviar todos los toques? esta vista superpuesta? Es un subcalso de UIView.

Sol
fuente
2
¿Has resuelto tu problema? En caso afirmativo, acepte la respuesta para que sea más fácil para otros encontrar la solución porque estoy enfrentando el mismo problema.
Khushbu Desai
esta respuesta funciona bien stackoverflow.com/a/4010809/4833705
Lance Samaria

Respuestas:

121

¡Desactivar la interacción del usuario era todo lo que necesitaba!

C objetivo:

myWebView.userInteractionEnabled = NO;

Rápido:

myWebView.isUserInteractionEnabled = false
rmp251
fuente
Esto funcionó en mi situación en la que tenía una subvista del botón. Inhabilité la interacción en la subvista y el botón funcionó.
simple_code
2
En Swift 4,myWebView.isUserInteractionEnabled = false
Louis 'LYRO' Dupont el
117

Para pasar toques desde una vista superpuesta a las vistas debajo, implemente el siguiente método en la vista UIV:

C objetivo:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"Passing all touches to the next view (if any), in the view stack.");
    return NO;
}

Swift 5:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    print("Passing all touches to the next view (if any), in the view stack.")
    return false
}
PixelCloudSt
fuente
3
Tengo el mismo problema, pero lo que quiero es que si estoy haciendo zoom / girando / moviendo la vista con gesto, debería devolver sí y para el evento de reposo debería devolver no, ¿cómo puedo lograr esto?
Minakshi
@Minakshi esa es una pregunta totalmente diferente, haz una nueva pregunta para eso.
Fattie
pero tenga en cuenta que este enfoque simple NO LE PERMITE TENER botones, etc. ¡EN LA PARTE SUPERIOR de la capa en cuestión!
Fattie
56

Este es un hilo viejo, pero surgió en una búsqueda, así que pensé en agregar mi 2c. Tengo una UIView de cobertura con subvistas, y solo quiero interceptar los toques que golpean una de las subvistas, así que modifiqué la respuesta de PixelCloudSt a

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView* subview in self.subviews ) {
        if ( [subview hitTest:[self convertPoint:point toView:subview] withEvent:event] != nil ) {
            return YES;
        }
    }
    return NO;
}
fresido
fuente
1
Debe crear una subclase de UIView personalizada (o una subclase de cualquier otra subclase de UIView como UIImageView o lo que sea) y anular esta función. En esencia, la subclase puede tener una 'interfaz' completamente vacía en el archivo .h, y la función anterior es todo el contenido de la 'implementación'.
Fresidue
Gracias, esto es exactamente lo que necesitaba para pasar toques a través del espacio vacío de mi UIView, para ser manejado por la vista detrás. +100
Danny
41

Versión mejorada de @fresidue answer. Puede usar esta UIViewsubclase como vista transparente pasando toques fuera de su subvista. Implementación en Objective-C :

@interface PassthroughView : UIView

@end

@implementation PassthroughView

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView *view in self.subviews) {
        if (!view.hidden && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
            return YES;
        }
    }
    return NO;
}

@end

.

y en Swift:

class PassthroughView: UIView {
  override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return subviews.contains(where: {
      !$0.isHidden
      && $0.isUserInteractionEnabled
      && $0.point(inside: self.convert(point, to: $0), with: event)
    })
  }
}

PROPINA:

Supongamos que tiene un panel grande de "soporte", tal vez con una vista de tabla detrás. Usted hace el panel "titular" PassthroughView. Ahora funcionará, puede desplazarse por la tabla "a través" del "titular".

¡Pero!

  1. En la parte superior del panel "titular" tiene algunas etiquetas o iconos. No se olvide, por supuesto, esos deben simplemente estar marcados. La interacción del usuario está habilitada.

  2. En la parte superior del panel "titular" tiene algunos botones. No se olvide, por supuesto, ¡esos deben estar marcados como la interacción del usuario habilitada ENCENDIDA!

  3. Nota que tanto confusa, el "titular" en sí - la vista se utiliza PassthroughViewen - la interacción del usuario debe estar marcado activado EN ! Eso está encendido ! (De lo contrario, el código en PassthroughView simplemente nunca se llamará).

Timur Bernikovich
fuente
1
He usado la solución dada para Swift. Está funcionando en iOS 11 pero se bloquea cada vez en iOS 10. Muestra un mal acceso. ¿Alguna idea?
Soumen
Me salvaste los días, funcionó para dar toques a las siguientes UIViews.
AaoIi
1
No se olvide de verificar también$0.isUserInteractionEnabled
Sean Thielen
7

Tuve un problema similar con un UIStackView (pero podría ser cualquier otra vista). Mi configuración fue la siguiente: Controlador de vista con containerView y StackView

Es un caso clásico en el que tengo un contenedor que debía colocarse en el fondo, con botones a los lados. Para fines de diseño, incluí los botones en un UIStackView, pero ahora la parte central (vacía) de la pilaView intercepta toques :-(

Lo que hice fue crear una subclase de UIStackView con una propiedad que define la subvista que debería ser tocable. Ahora, cualquier toque en los botones laterales (incluido en la matriz * viewsWithActiveTouch *) se dará a los botones, mientras que cualquier toque en la vista de pila en cualquier otro lugar que no sea estas vistas no se interceptará y, por lo tanto, se pasará a lo que esté debajo de la pila ver.

/** Subclass of UIStackView that does not accept touches, except for specific subviews given in the viewsWithActiveTouch array */
class NoTouchStackView: UIStackView {

  var viewsWithActiveTouch: [UIView]?

  override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    if let activeViews = viewsWithActiveTouch {
        for view in activeViews {
           if CGRectContainsPoint(view.frame, point) {
                return view

           }
        }
     }
     return nil
   }
}
Frédéric Adda
fuente
6

Si la vista a la que desea reenviar los toques no es una subvista / supervista, puede configurar una propiedad personalizada en su subclase UIView de la siguiente manera:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

Asegúrate de sintetizarlo en tu .m:

@synthesize forwardableTouchee;

Y luego incluya lo siguiente en cualquiera de sus métodos de UIResponder como:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    [self.forwardableTouchee touchesBegan:touches withEvent:event];

}

Donde quiera que instale su UIView, configure la propiedad forwardableTouchee en la vista a la que desea que se reenvíen los eventos:

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;
Chris Ladd
fuente
4

En Swift 5

class ThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard let slideView = subviews.first else {
            return false
        }

        return slideView.hitTest(convert(point, to: slideView), with: event) != nil
    }
}
onmyway133
fuente
4

Necesitaba pasar toques a través de un UIStackView. Un UIView dentro era transparente, pero el UIStackView consumió todos los toques. Esto funcionó para mí:

class PassThrouStackView: UIStackView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view == self {
            return nil
        }
        return view
    }
}

Todas las Subvistas organizadas todavía reciben toques, pero los toques en el UIStackView mismo pasaron a la vista a continuación (para mí, un mapView).

gorkem
fuente
2

Parece que incluso si tienes bastantes respuestas aquí, no hay nadie limpio en la rapidez que necesitaba. Así que tomé la respuesta de @fresidue aquí y la convertí en rápida, ya que es lo que ahora la mayoría de los desarrolladores quieren usar aquí.

Solucionó mi problema donde tengo una barra de herramientas transparente con botón, pero quiero que la barra de herramientas sea invisible para el usuario y que los eventos táctiles deberían pasar.

isUserInteractionEnabled = false ya que algunos declararon que no es una opción basada en mis pruebas.

 override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.hitTest(convert(point, to: subview), with: event) != nil {
            return true
        }
    }
    return false
}
Renetik
fuente
1

Tenía un par de etiquetas dentro de StackView y no tuve mucho éxito con las soluciones anteriores, en su lugar resolví mi problema usando el siguiente código:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

Subclases UIStackView:

class PassThrouStackView:UIStackView{
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        for subview in self.arrangedSubviews {
            let convertedPoint = convert(point, to: subview)
            let labelPoint = subview.point(inside: convertedPoint, with: event)
            if (labelPoint){
                return subview
            }

        }
        return nil
    }

}

Entonces podrías hacer algo como:

class ResponsiveLabel:UILabel{
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Respond to touch
    }
}
Amir.n3t
fuente
0

Intenta algo como esto ...

for (UIView *view in subviews)
  [view touchesBegan:touches withEvent:event];

El código anterior, en su método touchesBegan, por ejemplo, pasaría los toques a todas las subvistas de vista.

Jordán
fuente
66
El problema con este enfoque AFAIK es que tratar de reenviar toques a las clases del marco UIKit a menudo no tendrá ningún efecto. De acuerdo con la documentación de Apple, las clases de marco UIKit no están diseñadas para recibir toques que tengan la propiedad de vista establecida en alguna otra vista. Por lo tanto, este enfoque funciona principalmente para subclases personalizadas de UIView.
maciejs
0

La situación que intentaba hacer era construir un panel de control usando controles dentro de UIStackView anidados. Algunos de los controles tenían otros de UITextField con UIButton. Además, había etiquetas para identificar los controles. Lo que quería hacer era poner un gran botón "invisible" detrás del panel de control para que si un usuario tocaba un área fuera de un botón o campo de texto, pudiera atraparlo y tomar medidas, principalmente descartar cualquier teclado si aparece un texto el campo estaba activo (resignFirstResponder). Sin embargo, al tocar una etiqueta u otra área en blanco en el panel de control no pasaría nada. Las discusiones anteriores fueron útiles para llegar a mi respuesta a continuación.

Básicamente, subclasifiqué UIStackView y sobrescribí la rutina "point (inside: with) para buscar el tipo de controles que necesitaban tocar e" ignorar "cosas como etiquetas que quería ignorar. También verifica el interior de UIStackView para que las cosas puedan repetirse en la estructura del panel de control.

El código es quizás un poco más detallado de lo que debería ser. Pero fue útil para la depuración y, con suerte, proporciona más claridad sobre lo que está haciendo la rutina. Solo asegúrese de que en Interface Builder cambie la clase de UIStackView's a PassThruStack.

class PassThruStack: UIStackView {

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

    for view in self.subviews {
        if !view.isHidden {
            let isStack = view is UIStackView
            let isButton = view is UIButton
            let isText = view is UITextField
            if isStack || isButton || isText {
                let pointInside = view.point(inside: self.convert(point, to: view), with: event)
                if pointInside {
                    return true
                }
            }
        }
    }
    return false
}

}

anorskdev
fuente
-1

Según lo sugerido por @PixelCloudStv si desea lanzar tocado de una vista a otra pero con un control adicional sobre este proceso - subclase UIView

//encabezamiento

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//implementación

#import "TouchView.h"

@implementation TouchView

#pragma mark - Ovverride

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL moveTouch = YES;
    if (CGRectContainsPoint(self.activeRect, point)) {
        moveTouch = NO;
    }
    return moveTouch;
}

@end

Después en interfaceBuilder, simplemente establezca la clase de Vista en TouchView y configure el rect activo con su rect. También puedes cambiar e implementar otra lógica.

gbk
fuente