如何从UIScrollView中窃取触摸事件?

40
今天我利用自己的创造时间,研究了如何从UIScrollView窃取触摸并立即将它们发送到特定的子视图,同时保持其余部分的默认行为。考虑在UITableView中放置UIPickerView。默认情况下,如果您在选择器视图上拖动手指,则滚动视图将滚动,而选择器视图将保持不变。
我尝试的第一件事是覆盖重写。
- (BOOL)touchesShouldCancelInContentView:(UIView *)view

可以简单地禁止UIScrollView在选择器视图内取消触摸事件。这样做是可行的,但有一个不愉快的副作用。你希望选择器视图能够立即响应,因此必须将delaysContentTouches设置为NO。问题在于,你不希望其余的表格视图也立即响应,因为如果这样做,表格视图单元格将始终在滚动开始前高亮显示几毫秒。

我尝试的第二件事是覆盖重写

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

因为我曾经读过滚动视图总是会返回自己,从而会“窃取”其子视图的触摸事件,如果这些事件对于滚动视图不重要,它将稍后将它们发送给子视图。然而,这种说法已经不再正确。UIScrollView的默认hitTest:withEvent: 实现实际上会返回应该接收触摸事件的子视图。相反,它使用手势识别器来拦截触摸事件。

所以我尝试的第三件事情是实现自己的手势识别器,并在触摸事件在选择器视图之外时使其失败,在选择器视图内时使其成功。然后,我使用以下代码将所有滚动视图的手势识别器设置为只有当我的手势识别器失败时才失败:

for (UIGestureRecognizer * gestureRecognizer in self.tableView.gestureRecognizers)
{
    [gestureRecognizer requireGestureRecognizerToFail:myRecognizer];
}

实际上,这确实从滚动视图中窃取了触摸事件,但选择器视图却从未收到它们。因此,我想也许我可以使用以下代码将手势识别器接收到的所有触摸事件发送出去:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches)
        [touch.view touchesBegan:touches withEvent:event];
}

以上代码是一个简化版。我还确保视图是选择器视图(或其子视图之一),并像我上面提到的那样设置手势识别器的适当状态。我同样对于取消、结束和移动时也做了相同的处理。然而,选择器视图仍然没有响应。

在回到我的正常工作之前,我还尝试了最后一件事情。在广泛搜索后,我读到嵌套的UIScrollViews自3.x开始就可以神奇地工作,因此我将我的选择器视图放在了嵌套的UIScrollView中,并在其上设置了以下属性:

scrollView.delaysContentTouches = NO;
scrollView.canCancelContentTouches = NO;
如预期所料,外部滚动视图对待内部滚动视图与对待选择器视图一样,导致内部滚动视图无法接收到触摸事件。我知道UIScrollView具有名为UIScrollViewDelayedTouchesBeganGestureRecognizer的手势识别器,该手势识别器会在150(?)毫秒后拦截触摸事件,并将其发送到适当的子视图。我认为我应该能够编写类似的手势识别器,使滚动视图的默认识别器失败,并立即将触摸事件发送到选择器视图。如果有人知道如何编写这样的手势识别器,请告诉我,如果您有其他解决问题的方法,也可以分享一下。感谢您阅读整个问题,即使您不知道答案,也可以给问题点赞,以便它得到更多关注(希望来自能够回答的人)。谢谢! :)
3个回答

36
有时你需要在找到答案之前先问出问题。丹·雷也遇到过类似的问题,但解决方法却非常不同。
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView* result = [super hitTest:point withEvent:event];

    if ([result.superview isKindOfClass:[UIPickerView class]])
    {
        self.scrollEnabled = NO;
    }
    else 
    {
        self.scrollEnabled = YES;    
    }
    return result;
}

我已经测试了代码,它对我也很好用。但是,这并不是真正从滚动视图中窃取触摸事件,如果有人知道如何实际窃取触摸事件,那就太好了。
来源: UIPickerView inside UITableView.tableFooterView doesn't receive drag touches

既然这仍然是唯一的答案,我将接受它作为我的答案,但如果您有更好的解决方案,请不要犹豫分享! - Erik B
这是我首选的解决方案。它简洁高效,而且非常好用。非常感谢! - DrMickeyLauer
1
非常好,对于我的需求,我将其更改为 ([result isKindOfClass:[UITableView class]] || [result.superview isKindOfClass:[UITableView class]] || [result.superview isKindOfClass:[UITableViewCell class]]) - Diziet

4

4
当您回答问题时,最好将文章中相关部分包含在您的答案中,而不是直接链接。 - brandonscript

2

我也来晚了,但对于新手而言,如果你只是想完全忽略滚动视图上的滑动操作,那么我成功的方法是在我想要忽略滑动操作的视图上添加一个平移手势识别器,就像这样:

let panGesture = UIPanGestureRecognizer()
panGesture.cancelsTouchesInView = false
myView?.addGestureRecognizer(panGesture)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接