Permitir que UIScrollView y sus subviews respondan a un toque


Quiero que tanto mi UIScrollView como sus subvistas reciban todos los eventos táctiles dentro de la subvista. Cada uno puede responder a su manera.

Alternativamente, si los gestos de toque se reenvían a subviews, todo estaría bien.

Mucha gente está luchando en esta área general. He aquí algunas de las muchas preguntas relacionadas:

¿Cómo UIScrollView robar detalles de su subvistas
Cómo robar toques de UIScrollView?
Cómo Cancelar Desplazamiento en UIScrollView

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

1) En ese momento, no se si es un toque o no.
2) A veces necesito establecer userInteractionEnabled en NO.

EDITAR: Para aclarar, sí, quiero tratar los grifos de manera diferente a las cacerolas. Los grifos deben ser manejados por subviews. Las cacerolas pueden ser manejadas por vista de desplazamiento de la manera habitual.

Author: Community, 2012-07-14

5 answers

Primero, un descargo de responsabilidad. Si establece userInteractionEnabled a NO en el UIScrollView, no se pasarán eventos táctiles a las subviews. Hasta donde sé, no hay manera de evitarlo con una excepción: interceptar eventos táctiles en la supervisión de UIScrollView, y específicamente pasar esos eventos a las subviews de UIScrollView. Para ser honesto, no se por qué querrías hacer esto. Si desea deshabilitar la funcionalidad específica de UIScrollView (como...bueno, desplazamiento) usted puede hacer eso fácilmente sin desactivación de la interacción del usuario.

Si entiendo su pregunta, necesita tocar eventos para ser procesados por UIScrollView y pasado a las subviews? En cualquier caso (sea cual sea el gesto), creo que lo que estás buscando es el método de protocolo gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: en el protocolo UIGestureRecognizerDelegate. En sus subviews, sean cuales sean los reconocedores de gestos que tenga, establezca un delegado (probablemente sea cual sea la clase que esté configurando el UIGestureReconginzer en el primer lugar) en el reconocedor de gestos. Sobreescriba el método anterior y devuelva YES. Ahora, este gesto será reconocido junto con cualquier otro reconocedor que podría haber 'robado' el gesto (en su caso, un toque). Usando este método, incluso puede ajustar su código para enviar solo ciertos tipos de gestos a las subviews o enviar el gesto solo en ciertas situaciones. Te da mucho control. Solo asegúrese de leer sobre el método, especialmente esta parte:

Este método es llamado cuando el reconocimiento de un gesto por ya sea gestureRecognizer o otherGestureRecognizer podría bloquear el otro reconocedor de gestos de reconocer su gesto. Tenga en cuenta que devolver YES está garantizado para permitir el reconocimiento simultáneo; devolver NO, por otro lado, no está garantizado para evitar reconocimiento simultáneo porque el otro reconocedor de gestos el delegado puede devolver SÍ.

Por supuesto, hay una advertencia: Esto solo se aplica a los reconocedores de gestos. Así que aún puede tener problemas si está tratando de usar touchesBegan:, touchesEnded, etc para procesar los toques. Puedes, por supuesto, usar hitTest: para enviar eventos raw touch a las subviews, pero ¿por qué? ¿Por qué procesar los eventos usando esos métodos en UIView, cuando se puede adjuntar un UIGestureRecognizer a una vista y obtener toda esa funcionalidad de forma gratuita? Si necesita toques procesados de una manera que ningún estándar UIGestureRecognizer puede proporcionar, subclase UIGestureRecognizer y procesar los toques allí. De esa manera se obtiene toda la funcionalidad de un UIGestureRecognizer junto con su propio procesamiento táctil personalizado. Realmente creo que Apple pretendía UIGestureRecognizer reemplazar la mayoría (si no todo) 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 de manejar al mitigar qué código procesa qué evento táctil.

 38
Author: Aaron Hayman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-19 16:11:18

No se si esto puede ayudarlo, pero tuve un problema similar, donde quería que el scrollview manejara el doble toque, pero reenvíe un solo toque a las subviews. Aquí está 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 efectivo debe cambiarse, pero en este punto debe tener toda la información que necesita para decidir si tiene que reenviar el evento. Es posible que necesite implementar esto en otro método como touchesMoved: withEvent:.

Espero que esto pueda ayudar.

 3
Author: Oyashiro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-17 16:01:18

Estaba teniendo este mismo problema, pero con un scrollview que estaba dentro de UIPageViewController, por lo que tuvo que ser manejado de manera ligeramente diferente.

Cambiando la propiedad cancelsTouchesInView a false para cada reconocedor en el UIScrollView pude recibir toques a los botones dentro del UIPageViewController.

Lo hice añadiendo este código a 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
}
 2
Author: Hunter Maximillion Monk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-12 03:25:57

Si lo que necesita es diferir entre un toque y un desplazamiento, puede probar si los toques se han movido. Si se trata de un toque a continuación, touchHasBeenMoved no se llamará entonces usted puede asumir que esto es un toque.

En este punto, puede establecer un booleano para indicar si un movnent se acumuló y establecer 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 tarde.

 0
Author: shannoga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-14 16:34:12

Una forma hackish de lograr tu objetivo-no 100% exacta-es subclasificar la ventana UIWindow y anular el evento - (void)sendEvent: (UIEvent*);

Un ejemplo rápido:

En SecondResponderWindow.h cabecera

//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 se ajusta al 100% a lo que esperaba, porque su vista de segunda respuesta no maneja el evento táctil de forma nativa a través de - touchDidBegin: más o menos, y tiene que implementar la segunda respuesta Windowdelegate. Sin embargo, este hack le permite manejar eventos táctiles en respondedores adicionales.

Este método está inspirado y extendido de la ventana de Tapdetecting de MITHIN KUMAR

 0
Author: xingzhi.sg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-19 01:11:47