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
Allowing interaction with a UIView under another UIView
提问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 onlyRespondToTouchesInSubviews
to YES
.
然后在任何可能使用普通UIView
. 配置它很简单,只要设置onlyRespondToTouchesInSubviews
到YES
。
回答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 项目,可在此处获得:
回答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 UIButton
or UIView
will pass touch events that were executed on a transparent pixel.
最近我写了一个课程来帮助我解决这个问题。将其用作UIButton
或UIView
将传递在透明像素上执行的触摸事件的自定义类。
This solution is a somewhat better than the accepted answer because you can still click a UIButton
that is under a semi transparent UIView
while the non transparent part of the UIView
will still respond to touch events.
此解决方案比接受的答案要好一些,因为您仍然可以单击UIButton
半透明下的 a,UIView
而 的非透明部分UIView
仍将响应触摸事件。
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 UIButton
underneath.
正如您在 GIF 中看到的,Giraffe 按钮是一个简单的矩形,但透明区域上的触摸事件会传递到UIButton
下方的黄色。
回答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 UIView
and 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