ios 允许与另一个 UIView 下的 UIView 交互

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1694529/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:36:03  来源:igfitidea点击:

Allowing interaction with a UIView under another UIView

objective-cioscocoa-touch

提问by delany

Is there a simple way of allowing interaction with a button in a UIView that lies under another UIView - where there are no actual objects from the top UIView on top of the button?

是否有一种简单的方法可以允许与位于另一个 UIView 下的 UIView 中的按钮进行交互 - 按钮顶部的顶部 UIView 没有实际对象?

For instance, at the moment I have a UIView (A) with an object at the top and an object at the bottom of the screen and nothing in the middle. This sits on top of another UIView that has buttons in the middle (B). However, I cannot seem to interact with the buttons in the middle of B.

例如,目前我有一个 UIView (A),顶部有一个对象,屏幕底部有一个对象,中间什么也没有。它位于另一个 UIView 之上,中间有按钮 (B)。但是,我似乎无法与 B 中间的按钮进行交互。

I can see the buttons in B - I've set the background of A to clearColor - but the buttons in B do not seem to receive touches despite the fact that there are no objects from A actually on top of those buttons.

我可以看到 B 中的按钮 - 我已将 A 的背景设置为 clearColor - 但 B 中的按钮似乎没有收到触摸,尽管实际上这些按钮顶部没有来自 A 的对象。

EDIT- I still want to be able to interact with the objects in the top UIView

编辑- 我仍然希望能够与顶部 UIView 中的对象进行交互

Surely there is a simple way of doing this?

当然有一种简单的方法可以做到这一点?

采纳答案by gyim

You should create a UIView subclass for your top view and override the following method:

您应该为顶视图创建一个 UIView 子类并覆盖以下方法:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // UIView will be "transparent" for touch events if we return NO
    return (point.y < MIDDLE_Y1 || point.y > MIDDLE_Y2);
}

You may also look at the hitTest:event: method.

您还可以查看 hitTest:event: 方法。

回答by Stuart

While many of the answers here will work, I'm a little surprised to see that the most convenient, generic and foolproof answer hasn't been given here. @Ash came closest, except that there is something strange going on with returning the superview... don't do that.

虽然这里的许多答案都有效,但我有点惊讶地发现这里没有给出最方便、通用和万无一失的答案。@Ash 最接近,除了返回超级视图时发生了一些奇怪的事情......不要这样做。

This answer is taken from an answer I gave to a similar question, here.

这个答案取自我对类似问题的回答,here

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == self) return nil;
    return hitView;
}

[super hitTest:point withEvent:event]will return the deepest view in that view's hierarchy that was touched. If hitView == self(i.e. if there is no subview under the touch point), return nil, specifying that this view should notreceive the touch. The way the responder chain works means that the view hierarchy above this point will continue to be traversed until a view is found that will respond to the touch. Don'treturn the superview, as it is not up to this view whether its superview should accept touches or not!

[super hitTest:point withEvent:event]将返回该视图层次结构中被触及的最深的视图。如果hitView == self(即,如果有触摸点在无子视图),回报率nil,指定,这种观点应该接收触摸。响应者链的工作方式意味着该点之上的视图层次结构将继续遍历,直到找到响应触摸的视图。不要返回超级视图,因为它的超级视图是否应该接受触摸取决于这个视图!

This solution is:

这个解决方案是:

  • convenient, because it requires no references to any other views/subviews/objects;
  • generic, because it applies to any view that acts purely as a container for touchable subviews, and the configuration of the subviews does not affect the way it works (as it does if you override pointInside:withEvent:to return a particular touchable area).
  • foolproof, there's not much code... and the concept isn't difficult to get your head around.
  • 方便,因为它不需要引用任何其他视图/子视图/对象;
  • generic,因为它适用于任何纯粹充当可触摸子视图容器的视图,并且子视图的配置不会影响它的工作方式(就像您重写pointInside:withEvent:以返回特定的可触摸区域一样)。
  • 万无一失,代码不多……而且这个概念不难理解。


I use this often enough that I have abstracted it into a subclass to save pointless view subclasses for one override. As a bonus, add a property to make it configurable:

我经常使用它,以至于我将它抽象为一个子类,以便为一次覆盖保存无意义的视图子类。作为奖励,添加一个属性以使其可配置:

@interface ISView : UIView
@property(nonatomic, assign) BOOL onlyRespondToTouchesInSubviews;
@end

@implementation ISView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == self && onlyRespondToTouchesInSubviews) return nil;
    return hitView;
}
@end

Then go wild and use this view wherever you might use a plain UIView. Configuring it is as simple as setting onlyRespondToTouchesInSubviewsto YES.

然后在任何可能使用普通UIView. 配置它很简单,只要设置onlyRespondToTouchesInSubviewsYES

回答by Tyler

There are several ways you could handle this. My favorite is to override hitTest:withEvent: in a view that is a common superview (maybe indirectly) to the conflicting views (sounds like you call these A and B). For example, something like this (here A and B are UIView pointers, where B is the "hidden" one, that is normally ignored):

有几种方法可以处理这个问题。我最喜欢的是在一个视图中覆盖 hitTest:withEvent: 在一个视图中,该视图是冲突视图(听起来像您称这些 A 和 B)的公共超级视图(可能是间接的)。例如,像这样的东西(这里 A 和 B 是 UIView 指针,其中 B 是“隐藏”指针,通常被忽略):

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint pointInB = [B convertPoint:point fromView:self];

    if ([B pointInside:pointInB withEvent:event])
        return B;

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

You could also modify the pointInside:withEvent:method as gyim suggested. This lets you achieve essentially the same result by effectively "poking a hole" in A, at least for touches.

您也可以pointInside:withEvent:按照 gyim 的建议修改该方法。这使您可以通过有效地在 A 中“戳一个洞”来获得基本相同的结果,至少在触摸时是这样。

Another approach is event forwarding, which means overriding touchesBegan:withEvent:and similar methods (like touchesMoved:withEvent:etc) to send some touches to a different object than where they first go. For example, in A, you could write something like this:

另一种方法是事件转发,这意味着覆盖touchesBegan:withEvent:和类似的方法(如touchesMoved:withEvent:等)将一些触摸发送到与它们最初去的地方不同的对象。例如,在 A 中,你可以这样写:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if ([self shouldForwardTouches:touches]) {
        [B touchesBegan:touches withEvent:event];
    }
    else {
        // Do whatever A does with touches.
    }
}

However, this will not always workthe way you expect! The main thing is that built-in controls like UIButton will always ignore forwarded touches. Because of this, the first approach is more reliable.

但是,这并不总是按照您期望的方式工作!主要的是像 UIButton 这样的内置控件将始终忽略转发的触摸。因此,第一种方法更可靠。

There's a good blog post explaining all this in more detail, along with a small working xcode project to demo the ideas, available here:

有一篇很好的博客文章更详细地解释了所有这些,以及一个用于演示这些想法的小型工作 xcode 项目,可在此处获得:

http://bynomial.com/blog/?p=74

http://bynomial.com/blog/?p=74

回答by Benjamin Cox

You have to set upperView.userInteractionEnabled = NO;, otherwise the upper view will intercept the touches.

您必须设置upperView.userInteractionEnabled = NO;,否则上视图将拦截触摸。

The Interface Builder version of this is a checkbox at the bottom of the View Attributes panel called "User Interaction Enabled". Uncheck it and you should be good to go.

Interface Builder 版本是位于 View Attributes 面板底部的一个复选框,名为“User Interaction Enabled”。取消选中它,你应该很高兴。

回答by samvermette

Custom implementation of pointInside:withEvent: indeed seemed like the way to go, but dealing with hard-coded coordinates seemed odd to me. So I ended up checking whether the CGPoint was inside the button CGRect using the CGRectContainsPoint() function:

pointInside:withEvent: 的自定义实现确实是可行的方法,但处理硬编码坐标对我来说似乎很奇怪。所以我最终使用 CGRectContainsPoint() 函数检查 CGPoint 是否在按钮 CGRect 内:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    return (CGRectContainsPoint(disclosureButton.frame, point));
}

回答by Segev

Lately I wrote a class that will help me with just that. Using it as a custom class for a UIButtonor UIViewwill pass touch events that were executed on a transparent pixel.

最近我写了一个课程来帮助我解决这个问题。将其用作UIButtonUIView将传递在透明像素上执行的触摸事件的自定义类。

This solution is a somewhat better than the accepted answer because you can still click a UIButtonthat is under a semi transparent UIViewwhile the non transparent part of the UIViewwill still respond to touch events.

此解决方案比接受的答案要好一些,因为您仍然可以单击UIButton半透明下的 a,UIView而 的非透明部分UIView仍将响应触摸事件。

GIF

动图

As you can see in the GIF, the Giraffe button is a simple rectangle but touch events on transparent areas are passed on to the yellow UIButtonunderneath.

正如您在 GIF 中看到的,Giraffe 按钮是一个简单的矩形,但透明区域上的触摸事件会传递到UIButton下方的黄色。

Link to class

班级链接

回答by Dan Rosenstark

Just riffing on the Accepted Answer and putting this here for my reference. The Accepted Answer works perfectly. You can extend it like this to allow your view's subviews to receive the touch, OR pass it on to any views behind us:

只是重复接受的答案并将其放在这里供我参考。接受的答案完美地工作。您可以像这样扩展它以允许您的视图的子视图接收触摸,或者将其传递给我们身后的任何视图:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // If one of our subviews wants it, return YES
    for (UIView *subview in self.subviews) {
        CGPoint pointInSubview = [subview convertPoint:point fromView:self];
        if ([subview pointInside:pointInSubview withEvent:event]) {
            return YES;
        }
    }
    // otherwise return NO, as if userInteractionEnabled were NO
    return NO;
}

Note:You don't even have to do recursion on the subview tree, because each pointInside:withEvent:method will handle that for you.

注意:您甚至不必对子视图树进行递归,因为每个pointInside:withEvent:方法都会为您处理。

回答by Ash

I guess I'm a bit late to this party, but I'll add this possible solution:

我想我参加这个聚会有点晚了,但我会添加这个可能的解决方案:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView != self) return hitView;
    return [self superview];
}

If you use this code to override a custom UIView's standard hitTest function, it will ignore ONLY the view itself. Any subviews of that view will return their hits normally, and any hits that would have gone to the view itself are passed up to its superview.

如果您使用此代码覆盖自定义 UIView 的标准 hitTest 函数,它将仅忽略视图本身。该视图的任何子视图都将正常返回它们的命中,并且将进入视图本身的任何命中传递到其超视图。

-Ash

-灰

回答by Aditya

Setting userInteraction property disabled might help. Eg:

禁用 userInteraction 属性可能会有所帮助。例如:

UIView * topView = [[TOPView alloc] initWithFrame:[self bounds]];
[self addSubview:topView];
[topView setUserInteractionEnabled:NO];

(Note: In the code above, 'self' refers to a view)

(注意:在上面的代码中,'self'指的是一个视图)

This way, you can only display on the topView, but won't get user inputs. All those user touches will through this view and the bottom view will respond for them. I'd use this topView for displaying transparent images, or animate them.

这样,您只能在 topView 上显示,而不会获得用户输入。所有这些用户触摸都将通过此视图,底部视图将为它们做出响应。我会使用这个 topView 来显示透明图像,或者为它们设置动画。

回答by prodos

This approach is quite clean and allows that transparent subviewsare not reacting to touches as well. Just subclass UIViewand add the following method to its implementation:

这种方法非常干净,并且允许透明子视图也不会对触摸做出反应。只需子类化UIView并将以下方法添加到其实现中:

@implementation PassThroughUIView

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    for (UIView *v in self.subviews) {
        CGPoint localPoint = [v convertPoint:point fromView:self];
        if (v.alpha > 0.01 && ![v isHidden] && v.userInteractionEnabled && [v pointInside:localPoint withEvent:event])
            return YES;
    }
    return NO;
}

@end