如何忽略触摸事件并将其传递给另一个子视图的UIControl对象?

76
我有一个自定义的UIViewController,其UIView占据屏幕的一个角落,但是它大部分是透明的,除了一些按钮和其他东西。由于这个视图上的对象布局,该视图的框架可能会覆盖其中的一些按钮。如果触摸不在重要的地方,我希望能够忽略该视图上的任何触摸,但是我似乎只能传递实际的触摸事件(touchesEnded / nextResponder之类的)。如果我有一个不使用touchesEnded的UIButton或类似的东西,我如何将触摸事件传递给它?
我不能手动找到要调用的按钮选择器,因为这个自定义的ViewController可以用于许多不同的视图。我基本上需要一种方法来调用此方法:
[self.nextResponder touchesEnded:touches withEvent:event];
对于UIControl类型也适用。
7个回答

139

最好的方法是在您想要忽略触摸的视图中覆盖 hitTest:withEvent:。根据您的视图层次结构的复杂性,有几种简单的方法可以实现这一点。

如果您有一个引用视图,请参阅以下内容:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];

    // If the hitView is THIS view, return the view that you want to receive the touch instead:
    if (hitView == self) {
        return otherView;
    }
    // Else return the hitView (as it could be one of this view's buttons):
    return hitView;
}

如果你没有对该视图的引用:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];

    // If the hitView is THIS view, return nil and allow hitTest:withEvent: to
    // continue traversing the hierarchy to find the underlying view.
    if (hitView == self) {
        return nil;
    }
    // Else return the hitView (as it could be one of this view's buttons):
    return hitView;
}

我建议采用第一种方法,因为它最为稳健(如果可以获取底层视图的引用)。


那似乎是我想要做的,但我想要忽略触摸的视图是我创建的UIViewController的视图属性,所以有没有一种方法可以覆盖该视图的hitTest函数? - Chris C
4
@chris: 当然可以,只需要把它作为子类并像上面展示的那样添加重写方法。这意味着你会有一个额外的.h.m文件,里面代码很少,但它会完成任务。此外,一个穿透视图是你工具包中有用的子类。如果你的视图控制器在nib文件中创建视图,只需进入IB文件并在“identity inspector”中更改视图的类为你的新子类即可。如果是以编程方式创建,则在loadView中创建并分配你的视图子类(见编辑)。 - Stuart
太棒了!我已经寻找一个好的解决方案整整一天了。谢谢。 - Judioo
运行得很好,但我希望只有在事件是轻击事件而不是拖动事件时才返回otherView。是否有区分不同类型事件的方法? - htafoya
@htafoya:看起来您需要手势识别器。特定手势(例如轻触)的手势识别器只由其所附着的视图处理。通常,您应该使用触摸处理(hitTest:withEvent:)来决定是否应该完全接收触摸,但是使用手势识别器可以区分不同类型的触摸。 - Stuart
太棒了!对我很有用的技巧是使用 if hitView is UIControl 来测试点击是否在按钮上(可以适用于任何类型的控件)。如果是,则返回该视图,否则返回 nil 以通过触摸。 - Nitish K.

18

您还可以禁用用户交互,以便忽略触摸事件并将其传递给父级。

在Swift中:

yourView.isUserInteractionEnabled = false

4
我不认为那样是正确的。如果 isUserInteractionEnabled 为 true,则事件会被忽略,并且(根据 Apple 文档)会“从事件队列中移除”。也就是说,它们不会像 OP 想要的那样传递到下一个视图。 - John Scalo

18

快速回答:

你可以创建一个名为IgnoreTouchView的UIView子类。在故事板中,将其设置为VC视图上希望通过触摸传递的视图:

class IgnoreTouchView : UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let hitView = super.hitTest(point, with: event)
        if hitView == self {
            return nil
        }
        return hitView
    }
}

请注意,对于相关的UIView,您必须将UserInteractionEnabled设置为true!


Kevin,我把一些字符编辑到了最新的Swift版本April2017。(随意更改等)干杯。 - Fattie

8

我还没有找到一个很好的方法来在对象之间传递UITouch事件。通常更好的做法是实现hitTest方法,并且如果触摸点不在你想要处理的视图中,返回nil:

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

4

这是一个老问题,经常在搜索结果中排名靠前。所以我想发布另一个可能更适合你的解决方案。在我的情况下,我想要将一个标签拖动到另一个标签上面,并且需要获取下面的标签在hitTest中。最简单的方法是将 userInteractionEnabled 设置为 NOfalse,然后进行 hitTest 操作,如果需要再移动标签,则再次设置回去即可。以下是使用 Swift 的代码示例:

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first
    let location = touch?.locationInView(self.view)
    self.label1.userInteractionEnabled = false
    let view = self.view.hitTest(location!, withEvent: nil) as? UILabel
    print(view?.text)
    self.label1.userInteractionEnabled = true
}

感谢您提供的解决方案。 - Hiren
这样做是行不通的,因为一旦将label1.userInteractionEnabled设置为false,那么label1甚至无法后续检测到触摸事件。 - Frederic Adda
@FredA。这就是我写的原因:“...如果您需要再次移动它,则将其设置回来”。也许我表达不够清楚? - SmileBot
是的,你可以将它设置回去,但不能在touchesEnded方法内部。你可以在其他地方设置它,但不能在触摸方法内部设置。 - Frederic Adda
禁用用户交互后,无法进行命中测试。我刚试过了。 - Frederic Adda
显示剩余3条评论

3
我的问题类似:我有一个带有复杂触摸响应的UIControl,但当它嵌入到UIScrollView中时,它表现得很奇怪...
这可以防止父视图滚动(或者可以修改以防止其他交互),当子视图具有活动触摸手势时。
我的解决方案:
为子视图创建一个didBeginInteraction和didEndInteraction的委托协议。
从touchesBegan调用delegate.didBeginInteraction。
从touchesEnded和touchesCancelled调用delegate.didEndInteraction。
在父控制器中实现didBegin和didEnd协议,并设置委托。
然后在didBegin中设置scrollView.scrollEnabled = NO,在didEnd中设置scrollEnabled = YES。
希望这能对与问题不同的人有所帮助!

1
哇,我没想到我会找到解决我自定义UIControl的具体问题的方法。谢谢! - JustWorkAlready

1
我最容易的解决方法是将isUserInteractionEnabled设置为false,针对添加到按钮上的特定子视图。
// Some example view components
let button: UIButton = ...
let stackView: UIStackView = ...

// Setting this to false will make all touches ignored by this subview
stackView.isUserInteractionEnabled = false

// Maybe this was my specific case, but I had a pretty custom button with a bunch of subviews.
button.addSubview(stackView)

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