检测是否在其他UIView中触摸了特定的UIView

39
我有3个UIView,它们层叠在一个大的uiview上面。我想知道用户是否触摸了顶部的视图,并不关心其他视图。我会在第二个UIView中放置一些按钮,在第三个UIView中放置一个UITable。
问题是,我在第一个视图上打开userInteractionEngabled,并且它起作用,但所有其他视图都会以相同的方式响应,即使我关闭了它。如果我在self.view上禁用userInteractionEnabled,则它们都无法响应。我也无法检测到touchesBegan委托方法中触摸了哪个视图。
我的代码:
UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 150)];
aView = userInteractionEnabled = YES;
[self.view addSubview:aView];

UIView *bView = [[UIView alloc] initWithFrame:CGRectMake(0, 150, 320, 50)];
bView.userInteractionEnabled = NO;
[self.view addSubview:bView];

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//This gets called for a touch anywhere
}
8个回答

65

为了检查某个视图是否在另一个视图内被触摸,可以使用 hitTest。

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

在您自定义的touchesBegan实现中,检查touches集合中的每个触摸。可以使用hitTest方法获取点的位置。

- (CGPoint)locationInView:(UIView *)view;

这是一个方法,其中视图是您的超级视图(包含其他视图的那个)。

编辑:这是一个快速自定义实现:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 
    CGPoint locationPoint = [[touches anyObject] locationInView:self];
    UIView* viewYouWishToObtain = [self hitTest:locationPoint withEvent:event];
}

希望这对你有所帮助,保罗


抱歉,我知道你走在正确的轨道上,但我有一点难以理解你的意思。我从你的locationInView中获取了触摸点,理论上我可以通过这个找出用户触摸的哪个uiview,但我不确定它是如何调用hitTest的。 - Rudiger
2
需要注意的是,如果使用nil参数调用locationInView:方法,则会返回在主窗口坐标系中的CGPoint位置。 - Dafydd Williams

31

在我的情况下,我想要查找的UIView没有被hitTest返回。但以下代码效果更好:

    CGPoint locationPoint = [[touches anyObject] locationInView:self.view];
    CGPoint viewPoint = [myImage convertPoint:locationPoint fromView:self.view];
    if ([myImage pointInside:viewPoint withEvent:event]) {
       // do something
    }

我也遇到了被接受的答案存在的问题。出于某种原因,这个方法同样有效。请注意,我还尝试检测UIImageView上的点击事件,而不仅仅是UIView,如果这有任何相关性的话。 - Mic Fok

17

Swift代码:

 override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        if let touch = touches.first {
            if touch.view == self.view {
                self.hideInView()
            } else {
                return
            }

        }
    }

1
很棒的Swift答案。通过点赞分享爱心。 - Peter Kaminski
不会与另一个窗口的视图一起工作。例如,使用accessoryView。但是除此之外,这将起作用。 - Eyzuky

16
这也可以用以下方式完成。
首先,在您关心的视图中找到触摸位置:
CGPoint location = [touches.anyObject locationInView:viewYouCareAbout];

然后检查该点是否在视图范围内:

BOOL withinBounds = CGRectContainsPoint(viewYouCareAbout.bounds, location);

如果它在范围内,那么执行你的操作。

这可以通过一条语句来实现,例如可以直接在if语句中使用:

CGRectContainsPoint(viewYouCareAbout.bounds, [touches.anyObject locationInView:viewYouCareAbout])

9
touchesBegan 中,检查被触摸的视图是否是你想要的视图,解决了当我尝试确定用户是否触摸特定 ImageView 时遇到的问题。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    if ([touch view] ==  imageView ) {
        ... your code to handle the touch
    }
    ...

如果您调整视图尺寸以适应内容,请相应更改视图大小,否则即使在视图中没有内容的区域也会识别触摸事件。在我的情况下,当我尝试使用UIViewContentModeScaleAspectFit保持图像比例时,尽管图像已按所需方式进行了调整,但仍会捕获触摸事件的白色空间区域。

我该如何检查触摸区域是否有任何内容或是否有空白处? - Jabbar

8

我使用的swift版本来检查在infoView中发生触摸事件:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first, infoView.bounds.contains(touch.location(in: infoView))  else { return }
    print("touchesBegan") // Replace by your own code
} // touchesBegan

1
这对我有用:
func respondToGesture(gesture: UIGestureRecognizer) {
    let containsPoint = CGRectContainsPoint(targetView.bounds, gesture.locationInView(targetView))
}

1
当您的应用程序接收到触摸事件时,会启动一个命中测试过程来确定哪个视图应该接收事件。该过程从视图层次结构的根开始,通常是应用程序的窗口,并按照前后顺序搜索子视图,直到找到位于触摸下面的最前面的视图。那个视图成为命中测试视图并接收触摸事件。此过程中涉及的每个视图首先测试事件位置是否在其范围内。仅在测试成功后,视图才将事件传递给子视图进行进一步的命中测试。因此,如果您的视图在触摸下方但位于其父视图的边界之外,则父视图将无法测试事件位置,并且不会将触摸事件传递给您的视图。
解决此问题的一种方法是修改视图层次结构的布局,使您的视图位于其父视图的边界内。如果由于某些原因必须保留现有布局,则可以更改父视图的命中测试行为,以便它不会忽略触摸事件。这可以通过覆盖父视图类的-(UIView *)hitTest:withEvent:方法来实现。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    // Convert the point to the target view's coordinate system.
    // The target view isn't necessarily the immediate subview
    CGPoint pointForTargetView = [self.targetView convertPoint:point fromView:self];

    if (CGRectContainsPoint(self.targetView.bounds, pointForTargetView)) {

        // The target view may have its view hierarchy,
        // so call its hitTest method to return the right hit-test view
        return [self.targetView hitTest:pointForTargetView withEvent:event];
    }

    return [super hitTest:point withEvent:event]; }

这个问题的其他可能原因包括:

您的视图或其任何父视图的userInteractionEnabled属性被设置为NO。应用程序调用了它的beginIgnoringInteractionEvents方法,但没有匹配的endIgnoringInteractionEvents方法调用。如果您希望视图是交互式的,则应确保userInteractionEnabled属性设置为YES,并且应用程序不会忽略用户事件。

如果您的视图在动画期间没有接收到触摸事件,那么这是因为UIView的动画方法通常在动画进行时禁用触摸事件。当启动UIView动画时适当配置UIViewAnimationOptionAllowUserInteraction选项可以更改该行为。

另一个解决方案是

在视图中添加自定义实现

class CustomView: YourParentView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

        super.hitTest(point, with: event)
        return overlapHitTest(point: point, withEvent: event)
    }
}


extension UIView {

    func overlapHitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

        // 1
        if !self.isUserInteractionEnabled || self.isHidden || self.alpha == 0 {

            return nil
        }

        // 2
        var hitView: UIView? = self
        if !self.point(inside: point, with: event) {

            if self.clipsToBounds {

                return nil
            } else {

                hitView = nil
            }
        }

        // 3
        for subview in self.subviews.reversed() {

            let insideSubview = self.convert(point, to: subview)
            if let sview = subview.overlapHitTest(point: insideSubview, withEvent: event) {
                return sview
            }
        }
        return hitView
    }
}

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