23

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

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

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

UIScrollView 如何从其子视图
中窃取触摸 如何从 UIScrollView 窃取触摸?
如何在 UIScrollView 中取消滚动

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

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

编辑:为了澄清,是的,我想将水龙头与平底锅区别对待。点击应该由子视图处理。平底锅可以通过滚动视图以通常的方式处理。

4

5 回答 5

39

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

如果我理解您的问题,您需要 UIScrollView 处理点击事件并将其传递给子视图吗?无论如何(无论手势是什么),我认为您正在寻找的是协议中的协议方法gestureRecognizer :shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizerDelegate。在您的子视图中,无论您拥有什么手势识别器,都在手势识别器上设置一个委托(可能是UIGestureReconginzer首先设置的任何类)。覆盖上面的方法并返回YES. 现在,该手势将与任何其他可能“窃取”该手势的识别器一起被识别(在您的情况下,点击)。使用此方法,您甚至可以微调您的代码以仅向子视图发送某些类型的手势或仅在某些情况下发送手势。它给了你很多控制权。请务必阅读该方法,尤其是这一部分:

当gestureRecognizer 或otherGestureRecognizer 对手势的识别会阻止其他手势识别器识别其手势时,将调用此方法。注意返回YES保证允许同时识别;另一方面,返回 NO 并不能保证防止同时识别,因为其他手势识别器的委托可能会返回 YES。

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

于 2012-07-19T16:11:18.573 回答
3

我不知道这是否可以帮助你,但我有一个类似的问题,我希望滚动视图处理双击,但将单击转发到子视图。这是在a中使用的代码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:。

希望这能有所帮助。

于 2012-07-17T15:36:31.710 回答
3

我遇到了同样的问题,但是里面有一个滚动视图UIPageViewController,所以它的处理方式必须略有不同。

通过将cancelsTouchesInView属性更改为 false 上的每个识别器,UIScrollView我能够接收到对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
}
于 2016-04-12T03:25:57.313 回答
0

如果您需要区分触摸和滚动,那么您可以测试触摸是否已移动。如果这是一个点击,那么 touchHasBeenMoved 将不会被调用,那么你可以假设这是一个触摸。

此时,您可以设置一个布尔值来指示是否发生了移动,并将该布尔值设置为其他方法中的条件。

我在路上,但如果这是你需要的,我稍后会更好地解释。

于 2012-07-14T16:34:12.670 回答
0

实现您的目标的一种骇人听闻的方法 - 不是 100% 准确 - 是子类化 UIWindow 并覆盖 - (void)sendEvent:(UIEvent *)event;

一个简单的例子:

在 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

于 2012-07-19T01:11:47.067 回答