2012-07-14 15 views
20

Deseo que tanto mi UIScrollView como sus subvistas reciban todos los eventos táctiles dentro de la subvista. Cada uno puede responder a su manera.Permitir que UIScrollView y sus subvistas respondan a un toque

Alternativamente, si los gestos de tap fueron enviados a las subvistas, todo estaría bien.

Mucha gente está luchando en esta área general. Estas son algunas de las muchas preguntas relacionadas:

How does UIScrollView steal touches from its subviews
How to steal touches from UIScrollView?
How to Cancel Scrolling in UIScrollView

Por cierto, si puedo reemplazar hitTest: withEvent: en la vista de desplazamiento, veo los toques, siempre y cuando userInteractionEnabled es SÍ . Pero eso realmente no resuelve mi problema, porque:

1) En ese momento, no sé si es un grifo o no.
2) A veces necesito configurar userInteractionEnabled a NO.

EDITAR: Para aclarar, sí, quiero tratar los grifos de manera diferente a los recipientes. Los toques deben manejarse mediante subvistas. Las barras se pueden manejar con la vista de desplazamiento de la forma habitual.

+4

Ajuste userInteractionEnabled a NO y esperando datos de interacción del usuario parece una paradoja rompiendo universo. – borrrden

+0

¿Desea distinguir entre un toque y un desplazamiento? – shannoga

Respuesta

38

En primer lugar, un descargo de responsabilidad. Si configura userInteractionEnabled en NO en el UIScrollView, no se pasarán eventos táctiles a las subvistas. Hasta donde sé, no hay forma de evitarlo con una excepción: interceptar eventos táctiles en la supervista del UIScrollView, y pasar esos eventos específicamente a las subvistas de UIScrollView. Para ser honesto, sin embargo, no sé por qué querrías hacer esto. Si desea deshabilitar la funcionalidad específica de UIScrollView (como ... bueno, desplazamiento) puede hacerlo con la suficiente facilidad sin deshabilitar UserInteraction.

Si entiendo su pregunta, necesita eventos de tap para ser procesados ​​por UIScrollView y pasados ​​a las subvistas? En cualquier caso (sea cual sea el gesto), creo que lo que está buscando es el método de protocolo gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: en el protocolo UIGestureRecognizerDelegate. En sus subvistas, cualquiera que sea el reconocedor de gestos que tenga, configure un delegado (probablemente la clase que esté configurando el UIGestureReconginzer en primer lugar) en el reconocedor de gestos. Reemplace el método anterior y devuelva YES. Ahora, este gesto será reconocido junto con cualquier otro reconocedor que pueda haber "robado" el gesto (en su caso, un toque). Con este método, puedes incluso ajustar tu código para enviar solo ciertos tipos de gestos a las subvistas o enviar el gesto solo en ciertas situaciones. Te da mucho control. Sólo asegúrese de leer sobre el método, sobre todo esta parte:

Este método se llama cuando el reconocimiento de un gesto por ya sea gestureRecognizer o otherGestureRecognizer bloquearía la otra reconocedor gesto de reconocer su gesto. Tenga en cuenta que que vuelve SÍ garantiza la aceptación simultánea; que devuelve NO, por otra parte, no se garantiza que impida el reconocimiento simultáneo de porque el delegado del otro reconocedor de gestos puede devolver SÍ.

Por supuesto, hay una advertencia: Esto solo se aplica a los reconocedores de gestos. Por lo tanto, es posible que aún tenga problemas si intenta usar touchesBegan:, touchesEnded, etc. para procesar los toques. Por supuesto, puede usar hitTest: para enviar eventos táctiles en bruto a las subvistas, pero ¿por qué? ¿Por qué procesar los eventos usando esos métodos en UIView, cuando puede adjuntar un UIGestureRecognizer a una vista y obtener toda esa funcionalidad de forma gratuita? Si necesita retoques procesados ​​de una manera que ningún estándar UIGestureRecognizer puede proporcionar, subclaseUIGestureRecognizer y procese los toques allí. De esta forma, obtiene toda la funcionalidad de un UIGestureRecognizer junto con su propio procesamiento táctil personalizado. Realmente creo que Apple intentó UIGestureRecognizer para reemplazar la mayoría (si no todos) del código de procesamiento táctil personalizado que los desarrolladores usan en UIView. Permite la reutilización de código y es mucho más fácil tratar al mitigar qué código procesa qué evento táctil.

+1

¡Impresionante, gracias! Si alguien más está leyendo esto, es posible que pueda obtener un efecto similar con el método UIScrollView touchesShouldCancelInContentView –

+0

"Si desea deshabilitar la funcionalidad específica de UIScrollView (como ... bueno, desplazamiento) puede hacerlo con la suficiente facilidad sin deshabilitar UserInteraction "->" myScrollView.scrollEnabled = false " – RGML

0

Si lo que necesita es diferir entre un toque y un desplazamiento, entonces puede probar si los toques se han movido. Si esto es un toque, entonces no se invocará aHasBeenMoved, entonces puedes asumir que es un toque.

En este punto, puede establecer un valor booleano para indicar si un movimiento se acordó y configurar este booleano como una condición en sus otros métodos.

Estoy en el camino, pero si eso es lo que necesita, podré explicarlo mejor más adelante.

3

No sé si esto puede ayudarlo, pero tuve un problema similar, donde quería que la vista de desplazamiento manejara el doble toque, pero reenvió un solo toque a las subvistas. Este es el código utilizado en un CustomScrollView

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

    UITouch* touch = [touches anyObject]; 
    // Coordinates 
    CGPoint point = [touch locationInView:[self.subviews objectAtIndex:0]]; 

    // One tap, forward 
    if(touch.tapCount == 1){ 
     // for each subview 
     for(UIView* overlayView in self.subviews){ 
      // Forward to my subclasss only 
      if([overlayView isKindOfClass:[OverlayView class]]){ 
       // translate coordinate 
       CGPoint newPoint = [touch locationInView:overlayView]; 
       //NSLog(@"%@",NSStringFromCGPoint(newPoint)); 

       BOOL isInside = [overlayView pointInside:newPoint withEvent:event]; 
       //if subview is hit 
       if(isInside){ 
        Forwarding 
        [overlayView touchesEnded:touches withEvent:event]; 
        break; 
       } 
      } 
     } 

    } 
    // double tap : handle zoom 
    else if(touch.tapCount == 2){ 

     if(self.zoomScale == self.maximumZoomScale){ 
      [self setZoomScale:[self minimumZoomScale] animated:YES]; 
     } else { 
      CGRect zoomRect = [self zoomRectForScrollView:self withScale:self.maximumZoomScale withCenter:point];    

      [self zoomToRect:zoomRect animated:YES]; 
     } 

     [self setNeedsDisplay]; 

    } 
} 

Por supuesto, el código efectiva debe ser cambiado, pero en este momento usted debe tener todas las informaciones que necesita para decidir si tiene que reenviar el evento. Es posible que deba implementar esto en otro método como touchesMoved: withEvent :.

Espero que esto te pueda ayudar.

0

Una manera difícil de lograr su objetivo, no 100% exacta, es subclase de la ventana UI y anular el evento - (void) sendEvent: (UIEvent *);

Un ejemplo rápido:

en la cabecera SecondResponderWindow.h

//SecondResponderWindow.h 

@protocol SecondResponderWindowDelegate 
- (void)userTouchBegan:(id)tapPoint onView:(UIView*)aView; 
- (void)userTouchMoved:(id)tapPoint onView:(UIView*)aView; 
- (void)userTouchEnded:(id)tapPoint onView:(UIView*)aView; 
@end 

@interface SecondResponderWindow : UIWindow 
@property (nonatomic, retain) UIView *viewToObserve; 
@property (nonatomic, assign) id <SecondResponderWindowDelegate> controllerThatObserves; 
@end 

en SecondResponderWindow.m

//SecondResponderWindow.m 

- (void)forwardTouchBegan:(id)touch onView:(UIView*)aView { 
    [controllerThatObserves userTouchBegan:touch onView:aView]; 
} 
- (void)forwardTouchMoved:(id)touch onView:(UIView*)aView { 
    [controllerThatObserves userTouchMoved:touch onView:aView]; 
} 
- (void)forwardTouchEnded:(id)touch onView:(UIView*)aView { 
    [controllerThatObserves userTouchEnded:touch onView:aView]; 
} 

- (void)sendEvent:(UIEvent *)event { 
    [super sendEvent:event]; 

    if (viewToObserve == nil || controllerThatObserves == nil) return; 

    NSSet *touches = [event allTouches]; 
    UITouch *touch = [touches anyObject]; 
    if ([touch.view isDescendantOfView:viewToObserve] == NO) return; 

    CGPoint tapPoint = [touch locationInView:viewToObserve]; 
    NSValue *pointValue = [NSValue valueWithCGPoint:tapPoint]; 

    if (touch.phase == UITouchPhaseBegan) 
     [self forwardTouchBegan:pointValue onView:touch.view]; 
    else if (touch.phase == UITouchPhaseMoved) 
     [self forwardTouchMoved:pointValue onView:touch.view]; 
    else if (touch.phase == UITouchPhaseEnded) 
     [self forwardTouchEnded:pointValue onView:touch.view]; 
    else if (touch.phase == UITouchPhaseCancelled) 
     [self forwardTouchEnded:pointValue onView:touch.view]; 
} 

no es 100% conforme con lo que su esperaban - porque su segunda respuesta view no maneja el evento táctil de forma nativa a través de -touchDidBegin: más o menos, y tiene que implementar SecondResponderWindowDelegate. Sin embargo, este truco le permite manejar eventos táctiles en respondedores adicionales.

Este método se inspira y se extendía desde MITHIN de KUMAR TapDetectingWindow

2

Estaba teniendo el mismo problema, pero con una vista de desplazamiento que estaba dentro de UIPageViewController, por lo que tuvo que manejarse de forma ligeramente diferente.

Al cambiar la propiedad cancelsTouchesInView a falso para cada reconocedor en el UIScrollView, pude recibir toques de botones dentro del UIPageViewController.

lo hice añadiendo este código en viewDidLoad:

guard let recognizers = self.pageViewController.view.subviews[0].gestureRecognizers else { 
    print("No gesture recognizers on scrollview.") 
    return 
} 

for recognizer in recognizers { 
    recognizer.cancelsTouchesInView = false 
} 
Cuestiones relacionadas