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


68

我有一个自定义的UIViewController,其UIView占据了屏幕的一个角,但是除了其中有一些按钮和内容的部分之外,大多数视图都是透明的。由于该视图上对象的布局,该视图的框架可以掩盖其下方的一些按钮。我希望能够忽略该视图上的任何触摸,如果它们没有触摸任何重要的东西,但是我似乎只能传递实际的触摸事件(touchesEnded / nextResponder内容)。如果我有一个UIButton或类似的不使用touchesEnded的东西,如何将touch事件传递给它?

我不能只是手动找出要调用的按钮选择器,因为此自定义ViewController可以用于许多不同的视图。我基本上需要一种方法来称呼它:

[self.nextResponder touchesEnded:touches withEvent:event];

以及UIControl类型。

Answers:


135

可能最好的方法是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的view属性,那么有没有办法重写该视图的hitTest函数?
克里斯·C

4
@chris:当然,只是将其子类化并抛出上面所示的重写方法。这意味着你将有一个额外的.h.m文件挂在其代码非常少,但它会做的工作。此外,传递视图是工具包中有用的子类。如果您的视图控制器在中创建视图nib,则只需进入IB文件,然后在身份检查器中将视图的类更改为新的子类。如果是通过编程方式创建的,则在其中创建并分配您的视图子类loadView(请参见编辑)。
斯图尔特

很好的建议!我整天都在寻找一个好的解决方案。谢谢。
Judioo 2012年

效果很好,但是我希望仅当事件是轻击事件而不是拖动事件时才返回otherView。有没有办法区分不同类型的事件?
htafoya 2012年

@htafoya:听起来手势识别器正是您所需要的。特定手势(例如轻击)的手势识别器仅由其附着的视图处理。通常,您应该使用触摸处理(hitTest:withEvent:)来确定视图是否应该完全接受触摸,但是要使用手势识别器来区分不同类型的触摸。
斯图尔特(Stuart)

15

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

在Swift中:

yourView.isUserInteractionEnabled = false

3
我认为那是不对的。如果isUserInteractionEnabled为true,则事件将被忽略,并且(根据Apple文档)“已从事件队列中删除”。也就是说,它们不会像OP那样传递给下一个视图。
约翰·斯卡洛

14

迅捷答案:

您可以创建一个UIView子类IgnoreTouchView。在情节提要中,将其设置在您要通过触摸的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版本(2017年4月)。(随时更改,等等)干杯
Fattie

7

我还没有找到在对象之间传递UITouch事件的好方法。通常更好的方法是实现hitTest,如果该点不在您要处理的视图中,则返回nil:

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

4

这是一个古老的问题,它出现在搜索的顶部。因此,我想我会发布另一个可能更适合您的情况的解决方案。就我而言,我想将一个标签拖到另一个标签之上,并希望在hitTest中获得下面的标签。最简单的操作是将或设置userInteractionEnabled为,然后进行hitTest,然后在需要再次移动时再次将其重新设置。这是Swift中的代码示例:NOfalse

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
}

感谢您的解决方案。
希伦

这将不起作用,因为一旦将label1.userInteractionEnabled设置为false,则label1甚至以后都不会检测到触摸。
弗雷德里克·阿达

@FredA。这就是为什么我写道:“ ...如果需要再次移动它,然后将其重新设置”。也许我还不够清楚?
smileBot

是的,您可以重新设置它,但不能在touchesEnded内部。您可以在其他位置设置它,但不能在触摸方法内设置它。
弗雷德里克·阿达

@FredA。有用。我不确定是什么问题。触摸结束。禁用用户交互。做最快速的测试。将互动设置回启用。这是一个有据可查的方法。您必须做一些不同的事情。
smileBot

1

我的问题是类似的:我的UIControl具有复杂的触摸响应器,但是当嵌入滚动视图时,它表现得很有趣。

当子视图具有活动的触摸手势时,这可以防止父级滚动(或可以进行修改以防止其他交互)。

我的解决方案:

在子视图中为didBeginInteraction和didEndInteraction创建委托协议。

从touchesBegan调用Representative.didBeginInteraction

从touchesEnded和touchesCancelled调用proxy.didEndInteraction

在父控制器中,实现didBegin和didEnd协议,设置委托

然后在didBegin中设置scrollView.scrollEnabled = NO,在didEnd中设置scrollEnabled = YES。

希望这对使用案例与提出的问题有所不同的人有所帮助!


0

对我来说,简易解决方案是将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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.