允许UIScrollView及其子视图都响应触摸

时间:2012-07-14 15:43:46

标签: ios uiscrollview uigesturerecognizer touches

我希望我的UIScrollView及其子视图能够接收子视图中的所有触摸事件。每个人都可以以自己的方式回应。

或者,如果将点击手势转发到子视图,一切都会很好。

很多人都在这个一般领域挣扎。以下是许多相关问题中的一些:

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

顺便说一句,如果我在滚动视图中覆盖hitTest:withEvent:,只要userInteractionEnabled为YES,我就会看到触摸。但这并没有真正解决我的问题,因为:

1)此时,我不知道是否是一个水龙头 2)有时我需要将userInteractionEnabled设置为NO。

编辑:澄清,是的,我想以不同于平底锅的方式对待水龙头。点击应由子视图处理。可以通过滚动视图以通常的方式处理平移。

5 个答案:

答案 0 :(得分:38)

首先,免责声明。如果您在userInteractionEnabled上将NO设置为UIScrollView,则不会将触摸事件传递给子视图。据我所知,除了一个例外,没有办法解决这个问题:拦截UIScrollView超级视图上的触摸事件,并将这些事件专门传递给UIScrollView的子视图。说实话,我不知道你为什么要这样做。如果您想要禁用特定的UIScrollView功能(例如......好吧,滚动),您可以轻松地做到这一点,而无需禁用UserInteraction。

如果我理解您的问题,您需要通过传递给子视图的UIScrollView 来处理点击事件?在任何情况下(无论手势是什么),我认为您正在寻找的是协议UIGestureRecognizerDelegate中的协议方法gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:。在您的子视图中,无论您拥有什么样的手势识别器,都可以在手势识别器上设置一个委托(可能是在第一个位置设置UIGestureReconginzer的任何类)。覆盖上述方法并返回YES。现在,这个手势将被识别,以及可能“偷走”手势的任何其他识别器(在您的情况下,轻击)。使用此方法,您甚至可以对代码进行微调,以便仅将某些类型的手势发送到子视图,或仅在某些情况下发送手势。它给你很多控制权。请务必阅读有关该方法的内容,尤其是此部分:

  

在识别手势时调用此方法   gestureRecognizer或otherGestureRecognizer会阻止   其他手势识别器识别其手势。注意   返回YES保证允许同时识别;   另一方面,返回NO并不能保证会阻止   同时识别,因为其他手势识别器   代表可以返回YES。

当然,有一点需要注意:这仅适用于手势识别器。因此,如果您尝试使用touchesBegan:touchesEnded等来处理触摸,则可能仍会遇到问题。当然,您可以使用hitTest:将原始触摸事件发送到子视图,但为什么呢?为什么在UIView中使用这些方法处理事件时,您可以将UIGestureRecognizer附加到视图并免费获得所有这些功能?如果您需要以没有标准UIGestureRecognizer可以提供的方式处理的触摸,子类 UIGestureRecognizer并处理那里的触摸。这样,您就可以获得UIGestureRecognizer的所有功能以及您自己的自定义触摸处理功能。我真的认为Apple希望UIGestureRecognizer替换大多数(如果不是全部)开发人员在UIView上使用的自定义触摸处理代码。它允许代码重用,并且在减轻哪些代码处理触摸事件时处理起来要容易得多。

答案 1 :(得分:3)

我不知道这是否可以帮到你,但我遇到了类似的问题,我想让scrollview处理双击,但是转发单击到子视图。以下是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];

    }
}

当然,应该更改有效代码,但此时您应该拥有决定是否必须转发事件所需的所有信息。您可能需要在另一个方法中实现这个,如touchesMoved:withEvent:。

希望这可以提供帮助。

答案 2 :(得分:3)

我遇到了同样的问题,但是在UIPageViewController内有一个滚动视图,所以必须稍微处理一下。

通过将cancelsTouchesInView上的每个识别器的UIScrollView属性更改为false,我能够接收到UIPageViewController内按钮的触摸。

我是通过将此代码添加到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
}

答案 3 :(得分:0)

如果您需要在触摸和滚动之间有所不同,那么您可以测试触摸是否已被移动。如果这是一个点击,那么将不会调用touchHasBeenMoved然后你可以认为这是一个触摸。

此时,您可以设置一个布尔值来指示是否有一个movnent,并将此布尔值设置为其他方法中的条件。

我在路上,但如果这就是你需要的,我将能够在以后更好地解释。

答案 4 :(得分:0)

实现目标的一种hackish方式 - 不是100%完全 - 是子类化UIWindow并覆盖 - (void)sendEvent:(UIEvent *)事件;

一个简单的例子:

在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

在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];
}

这不是100%符合您的预期 - 因为您的第二个响应者视图不能通过-touchDidBegin本地处理触摸事件:左右,并且必须实现SecondResponderWindowDelegate。但是,这个hack确实允许您处理其他响应者的触摸事件。

这种方法的灵感来自MITHIN KUMAR的TapDetectingWindow