允许与另一个UIView下的UIView进行交互

有没有一种简单的方法允许与UIView中的一个button进行交互,该button位于另一个UIView之下 – 在顶部的UIView上没有实际的对象?

例如,目前我有一个UIView(A),顶部有一个对象,屏幕底部有一个对象,中间没有任何东西。 这位于另一个在中间(B)有button的UIView的顶部。 但是,我似乎无法与B中间的button进行交互。

我可以看到B中的button – 我已经将A的背景设置为clearColor – 但是B中的button似乎没有接触到触摸,尽pipe实际上在这些button之上没有A的对象。

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

当然有一个简单的方法来做到这一点?

你应该为你的顶视图创build一个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:方法。

虽然这里的许多答案都能起作用,但是我还是有点惊讶地发现,这里并没有给出最方便,通用和万无一失的答案。 @Ash最接近,除了返回超级视图之外还有一些奇怪的事情…不要这样做。

这个答案是从我给这个类似问题的答案中提取出来的。

 - (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 ,指定该视图不应该接收到触摸。 响应者链的工作方式意味着此点之上的视图层次将继续遍历,直到find将响应触摸的视图。 不要返回超级观点,因为它的超级观点是否应该接受这个观点是不合适的!

这个解决scheme是:

  • 方便 ,因为它不需要引用任何其他视图/子视图/对象;
  • 因为它适用于纯粹作为可触摸子视图的容器的任何视图,并且子视图的configuration不会影响其工作方式(如果您覆盖pointInside:withEvent:返回特定的可触摸区域)。
  • 万无一失 ,没有太多的代码……而这个概念并不难让你头脑发热。

我经常使用它,我已经抽象它到一个子类为一个覆盖保存无意义的观点子类。 作为奖励,添加一个属性,使其可configuration:

 @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 。 对其进行configuration与将onlyRespondToTouchesInSubviews设置为YES一样简单。

有几种方法可以处理这个问题。 我最喜欢的是重写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]; } 

您也可以修改pointInside:withEvent:方法作为gyimbuild议。 这可以让你通过在A中有效地“戳洞”来获得基本相同的结果,至less在触摸时是这样。

另一种方法是事件转发,意思是覆盖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项目来演示这些想法,可以在这里find:

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

你必须设置upperView.userInteractionEnabled = NO; ,否则上面的视图会拦截触摸。

这个界面生成器的版本是一个checkbox,位于View Attributes面板的底部,名为“User Interaction Enabled”。 取消它,你应该很好去。

pointInside的自定义实现:withEvent:确实看起来像要走的路,但处理硬编码的坐标对我来说似乎很奇怪。 所以我最终检查CGPoint是否在使用CGRectContainsPoint()函数的buttonCGRect中:

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

最近我写了一个能够帮助我的课程。 将它用作UIButtonUIView的自定义类将传递在透明像素上执行的触摸事件。

这个解决scheme比接受的答案好一些,因为你仍然可以点击一个半透明UIView下的UIButton ,而UIView的非透明部分仍然会响应触摸事件。

GIF

正如你在GIF中看到的,长颈鹿button是一个简单的矩形,但是透明区域上的触摸事件被传递到下面的黄色UIButton

链接到课堂

我想我晚了一点,但我会添加这个可能的解决scheme:

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

如果你使用这个代码覆盖自定义的UIView的标准hitTest函数,它将只忽略视图本身。 该视图的任何子视图将正常返回它们的匹配,并且任何已经到视图本身的匹配都会传递到它的超级视图。

-灰

只需轻轻接受答案,并把这里供我参考。 接受的答案完美的作品。 您可以像这样扩展它,使您的视图的子视图能够接收到触摸,或者将其传递给我们后面的任何视图:

 - (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; } 

注意:你甚至不必在子视图树上进行recursion,因为每个pointInside:withEvent:方法都会为你处理。

这种方法是相当干净的,并允许透明的子视图也不反应触摸。 只是子类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 

我的解决scheme是:

 -(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]; } 

希望这可以帮助

在两个视图中都可以用来拦截触摸。

顶视图:

 -(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. } 

但是,这是主意。

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

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

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

这样,你只能在topView上显示,但不会得到用户input。 所有这些用户触摸将通过这个观点,底部的观点将回应他们。 我会使用这个顶视图来显示透明的图像,或animation他们。

这是一个Swift版本:

 override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool { return !CGRectContainsPoint(buttonView.frame, point) } 

我从来没有使用UI工具包构build完整的用户界面,所以我没有太多的经验。 这是我认为应该工作。

每个UIView和这个UIWindow都有一个属性subviews ,它是一个包含所有子视图的NSArray。

您添加到视图的第一个子视图将接收索引0,下一个索引1等等。 你也可以使用insertSubview: atIndex:insertSubview:aboveSubview:replaceaddSubview:以及insertSubview: atIndex:子视图在层次结构中的位置的方法。

因此,请检查您的代码,以查看您首先添加到UIWindow的视图。 那将是0,另一个将是1。
现在,从你的一个子视图,到另一个你会做到以下几点:

 UIView * theOtherView = [[[self superview] subviews] objectAtIndex: 0]; // or using the properties syntax UIView * theOtherView = [self.superview.subviews objectAtIndex:0]; 

让我知道这是否适合您的情况!


(下面这个标记是我以前的答案):

如果视图需要相互通信,他们应该通过控制器(即使用stream行的MVC模型)来实现。

当你创build一个新的视图时,你可以确保它自己注册一个控制器。

所以这个技巧是要确保你的视图注册到一个控制器(可以通过名字或者你喜欢的字典或数组来存储它们)。 您可以让控制器为您发送消息,或者您可以获得对该视图的引用并直接与其通信。

如果你的视图没有链接回控制器(可能是这种情况),那么你可以使用单例和/或类方法来获取控制器的引用。

我认为正确的方法是使用内置到视图层次结构中的视图链。 对于推送到主视图的子视图,请不要使用通用的UIView,而应将UIView(或其中一个变体,如UIImageView)的子类设置为MYView:UIView(或任何所需的超types,如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中,然后执行这个touchesBegin来处理触摸。

请注意,触摸位置(CGPoint)会自动调整为相对于包含视图,因为它会弹回到视图层次链。

只是想发布这个,因为我有一些类似的问题,花了大量的时间试图在这里没有任何运气的答案。 我最终做了什么:

  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; } 

底部的视图是一个导航控制器,有一些赛格,我有一个门的顶部,可以closures平移手势。 整个事情都embedded了另一个VC。 像魅力一样工作。 希望这可以帮助。

Swift 3

 override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { for subview in subviews { if subview.frame.contains(point) { return true } } return false }