是否有一种简单的方法允许与位于另一个UIView下的UIView中的按钮进行交互 - 在按钮顶部没有来自顶部UIView的实际对象?
例如,目前我有一个UIView(A),顶部有一个对象,屏幕底部有一个对象,中间没有任何东西。它位于另一个UIView的顶部,中间有按钮(B)。但是,我似乎无法与B中间的按钮进行交互。
我可以看到B中的按钮 - 我已经将A的背景设置为clearColor - 但是B中的按钮似乎没有接触到,尽管实际上在这些按钮的顶部没有来自A的对象。
编辑 - 我仍然希望能够与顶级UIView中的对象进行交互
当然有一种简单的方法可以做到这一点吗?
答案 0 :(得分:97)
您应该为顶视图创建一个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);
}
您还可以查看hitTest:event:方法。
答案 1 :(得分:40)
虽然这里的许多答案都有效,但我有点惊讶地发现这里没有给出最方便,最通用和最简单的答案。 这个答案取自我对类似问题的回答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]
将返回该视图所触及的层次结构中最深的视图。如果hitView == self
(即如果触摸点下没有子视图),请返回nil
,指定此视图应不接收触摸。响应者链的工作方式意味着将继续遍历此点上方的视图层次结构,直到找到将响应触摸的视图。 不要返回超级视图,因为它的高级视图是否应该接受触摸不符合此视图!
pointInside:withEvent:
@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
。配置它就像将UIView
设置为onlyRespondToTouchesInSubviews
一样简单。
答案 2 :(得分:30)
有几种方法可以解决这个问题。我最喜欢的是覆盖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];
}
您也可以像gyim建议的那样修改pointInside:withEvent:
方法。这样可以通过在A中有效地“戳一个洞”来实现基本相同的结果,至少对于触摸来说。
另一种方法是事件转发,这意味着覆盖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.
}
}
然而,这并不总是以您期望的方式运作!最重要的是像UIButton这样的内置控件总是会忽略转发的触摸。因此,第一种方法更可靠。
有一篇很好的博客文章更详细地解释了这一切,还有一个小型的xcode项目来演示这些想法,可以在这里找到:
答案 3 :(得分:29)
您必须设置upperView.userInteractionEnabled = NO;
,否则上方视图将拦截触摸。
Interface Builder版本是View Attributes面板底部的一个复选框,名为“User Interaction Enabled”。取消选中它,你应该好好去。
答案 4 :(得分:11)
pointInside的自定义实现:withEvent:确实看起来像是要走的路,但处理硬编码坐标对我来说似乎很奇怪。所以我最后使用CGRectContainsPoint()函数检查CGPoint是否在CGRect按钮内:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return (CGRectContainsPoint(disclosureButton.frame, point));
}
答案 5 :(得分:8)
最近我写了一堂课,帮助我解决这个问题。将其用作UIButton
或UIView
的自定义类会传递在透明像素上执行的触摸事件。
此解决方案比接受的答案略胜一筹,因为您仍然可以点击半透明UIButton
下的UIView
,而UIView
的非透明部分仍会响应触摸事件。
正如您在GIF中看到的,长颈鹿按钮是一个简单的矩形,但透明区域上的触摸事件会传递到下面的黄色UIButton
。
答案 6 :(得分:4)
我想这个派对有点晚了,但我会添加这个可能的解决方案:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView != self) return hitView;
return [self superview];
}
如果您使用此代码覆盖自定义UIView的标准hitTest函数,它将仅忽略视图本身。该视图的任何子视图都会正常返回它们的命中,并且任何已经转到视图本身的命中都会被传递给它的超级视图。
-Ash
答案 7 :(得分:4)
刚刚接受了接受的答案并将其放在这里供我参考。接受的答案非常有效。您可以像这样扩展它以允许您的视图的子视图接收触摸,或将其传递给我们后面的任何视图:
- (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;
}
注意:您甚至不必在子视图树上进行递归,因为每个pointInside:withEvent:
方法都会为您处理。
答案 8 :(得分:3)
禁用设置userInteraction属性可能会有所帮助。例如:
UIView * topView = [[TOPView alloc] initWithFrame:[self bounds]];
[self addSubview:topView];
[topView setUserInteractionEnabled:NO];
(注意:在上面的代码中,&#39; self&#39;指视图)
这样,您只能在topView上显示,但不会获得用户输入。所有这些用户触摸都将通过此视图,底部视图将响应它们。我使用这个topView来显示透明图像,或者为它们制作动画。
答案 9 :(得分:3)
此方法非常简洁,并且透明子视图也不会对触摸作出反应。只需子类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
答案 10 :(得分:2)
你可以采取一些措施拦截两种观点中的触摸。
顶视图:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Do code in the top view
[bottomView touchesBegan:touches withEvent:event]; // And pass them on to bottomView
// You have to implement the code for touchesBegan, touchesEnded, touchesCancelled in top/bottom view.
}
但这就是主意。
答案 11 :(得分:2)
我的解决方案:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint pointInView = [self.toolkitController.toolbar convertPoint:point fromView:self];
if ([self.toolkitController.toolbar pointInside:pointInView withEvent:event]) {
self.userInteractionEnabled = YES;
} else {
self.userInteractionEnabled = NO;
}
return [super hitTest:point withEvent:event];
}
希望这有帮助
答案 12 :(得分:2)
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if subview.frame.contains(point) {
return true
}
}
return false
}
答案 13 :(得分:2)
这是一个Swift版本:
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
return !CGRectContainsPoint(buttonView.frame, point)
}
答案 14 :(得分:1)
我从未使用UI工具包构建完整的用户界面,因此我没有太多使用它的经验。这是我认为应该工作的。
每个UIView,以及UIWindow,都有一个属性subviews
,它是一个包含所有子视图的NSArray。
您添加到视图的第一个子视图将接收索引0,下一个索引1将依此类推。您还可以将addSubview:
替换为insertSubview: atIndex:
或insertSubview:aboveSubview:
以及可以确定子视图在层次结构中的位置的方法。
因此,请检查您的代码,看看您首先添加到UIWindow的视图。那将是0,另一个将是1.
现在,从您的一个子视图到另一个子视图,您将执行以下操作:
UIView * theOtherView = [[[self superview] subviews] objectAtIndex: 0];
// or using the properties syntax
UIView * theOtherView = [self.superview.subviews objectAtIndex:0];
请告诉我这是否适合您的情况!
(在此标记下面是我之前的回答):
如果视图需要相互通信,则应通过控制器(即使用流行的MVC模型)进行通信。
创建新视图时,可以确保它使用控制器注册自己。
因此,该技术是确保您的视图向控制器注册(控制器可以按名称或您喜欢的字典或数组存储它们)。您可以让控制器为您发送消息,也可以直接与视图进行通信。
如果您的视图没有链接回控制器(可能是这种情况),那么您可以使用singletons和/或类方法来获取对控制器的引用。
答案 15 :(得分:1)
let hitView = super.hitTest(point, with: event)
if hitView == self { return nil }
return hitView
答案 16 :(得分:1)
只想发布这个,因为我有点类似的问题,花了大量的时间试图在这里实现答案,没有任何运气。我最终做了什么:
for(UIGestureRecognizer *recognizer in topView.gestureRecognizers)
{
recognizer.delegate=self;
[bottomView addGestureRecognizer:recognizer];
}
topView.abView.userInteractionEnabled=NO;
并实施UIGestureRecognizerDelegate
:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
底部视图是一个带有多个segues的导航控制器,我的顶部有一扇门,可以用平移手势关闭。整件事嵌入了另一个VC。工作就像一个魅力。希望这会有所帮助。
答案 17 :(得分:1)
我认为正确的方法是使用视图层次结构中内置的视图链。 对于推到主视图上的子视图,不要使用泛型UIView,而是使用UIView(或其变体之一,如UIImageView)来创建MYView:UIView(或任何你想要的超类型,例如UIImageView)。在YourView的实现中,实现touchesBegan方法。触摸该视图时,将调用此方法。您在该实现中需要的只是一个实例方法:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event ;
{ // cannot handle this event. pass off to super
[self.superview touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event]; }
这个touchesBegan是一个响应者api,所以你不需要在你的公共或私人界面声明它;这是你必须要了解的那些神奇的api之一。这个self.superview最终将请求冒泡到viewController。然后,在viewController中,实现这个touchesBegan来处理触摸。
请注意,触摸位置(CGPoint)会相对于包围视图自动调整,因为它会在视图层次结构链中反弹。
答案 18 :(得分:0)
这是Stuart的绝妙的答案(主要是简单的答案)以及Segev的有用实现,这是一个Swift 4软件包,您可以将其放入任何项目中:
extension UIColor {
static func colorOfPoint(point:CGPoint, in view: UIView) -> UIColor {
var pixel: [CUnsignedChar] = [0, 0, 0, 0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let context = CGContext(data: &pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
context!.translateBy(x: -point.x, y: -point.y)
view.layer.render(in: context!)
let red: CGFloat = CGFloat(pixel[0]) / 255.0
let green: CGFloat = CGFloat(pixel[1]) / 255.0
let blue: CGFloat = CGFloat(pixel[2]) / 255.0
let alpha: CGFloat = CGFloat(pixel[3]) / 255.0
let color = UIColor(red:red, green: green, blue:blue, alpha:alpha)
return color
}
}
然后使用hitTest:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard UIColor.colorOfPoint(point: point, in: self).cgColor.alpha > 0 else { return nil }
return super.hitTest(point, with: event)
}