禁用对 UIView 背景的触摸,以便下部视图上的按钮可点击
Posted
技术标签:
【中文标题】禁用对 UIView 背景的触摸,以便下部视图上的按钮可点击【英文标题】:Disable touches on UIView background so that buttons on lower views are clickable 【发布时间】:2010-08-06 20:37:38 【问题描述】:基本上,这是我的视图层次结构(如果这很难阅读,我深表歉意......我是新来的,所以很乐意接受发布建议)
--AppControls.xib --------(UIView)ControlsView ----------------- (UIView)TopBar ----- -------------- btn1, btn2, btn3 ----------------- UIView)BottomBar ----- --------------slider1 btn1, btn2 --PageContent.xib ----------------- (UIView)ContentView ----- --------------btn1, btn2, btn3 ------------------(UIImageView)FullPageImage
我的情况是,我想在点击 PageContent 上不是按钮的任何位置时隐藏和显示控件并显示控件,就像 iPhone 视频播放器一样。但是,当显示控件时,我仍然希望能够单击 PageContent 上的按钮。
除了最后一点,我已经完成了所有这些工作。当控件显示背景时,控件的背景接收到触摸事件而不是下面的视图。关闭 ControlsView 上的用户交互会在其所有子项上关闭它。
我已经尝试在我的 ControlsView 子类上覆盖 HitTest,如下所示:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
UIView *hitView = nil;
NSArray *subviews = [self subviews];
int subviewCount = [subviews count];
for (int subviewIndex = 0; !hitView && subviewIndex < subviewCount; subviewIndex++)
hitView = [[subviews objectAtIndex:subviewIndex] hitTest:point withEvent:event];
return hitView;
但是,此时我的滑块不起作用,大多数其他按钮也不起作用,而且真的,事情开始变得奇怪了。
所以我的问题很简单:如何让一个视图的所有子视图都有触摸事件,而超级视图的背景是不可点击的,并且下面视图上的按钮可以接收触摸事件。
谢谢!
【问题讨论】:
【参考方案1】:你已经接近了。不要覆盖-hitTest:withEvent:
。在被调用的时候,事件调度器已经决定你的层次结构的子树拥有这个事件并且不会寻找其他地方。相反,覆盖-pointInside:withEvent:
,它在事件处理管道的早期调用。这就是系统询问“嘿,您的层次结构中的任何人此时是否响应事件?”的方式。如果您说“否”,则事件处理会在您下方的可见堆栈中继续进行。
根据documentation,默认实现只是检查该点是否在视图的边界内。
您的策略是当您的任何子视图位于该坐标时说“是”,但当触摸将触及背景时说“否”。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
for (UIView * view in [self subviews])
if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
return YES;
return NO;
【讨论】:
不客气。我曾经为同样的问题打了好几个小时。乐于助人。 当然。 :) 我不经常看到完全按照 SO 建模的显示层次结构,但是看到它是很有用的。仅供参考,这是一个有趣的调试技巧:在 gdb 中,当在断点处停止时,尝试运行po [myView recursiveDescription]
。它从那一点向下倾倒了整个层次结构。
这个答案结束了漫长而曲折的旅程,以允许重叠的视图来处理事件。非常感谢。
太棒了。非常感谢。我想知道为什么这么简单的行为还不是 UIKit 的一部分。
注意 - 如果子节点超出父节点的边界并且 CGpoint 在子节点中但在父节点之外,这可能会以意想不到的方式表现。快速测试表明,对于股票行为,这样的点被认为是外部的,但是通过这种修改,它被认为是内部。我这里的 cmets 代码没有空间,所以我会发布另一个答案。【参考方案2】:
感谢@Ben Zutto,Swift 3 解决方案:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool
for view in self.subviews
if view.isUserInteractionEnabled, view.point(inside: self.convert(point, to: view), with: event)
return true
return false
【讨论】:
或return subviews.first $0.isUserInteractionEnabled && $0.point(inside: convert(point, to: $0), with: event) != nil
注意:为了使其正常工作,还需要启用父母的用户交互【参考方案3】:
另一种方法可能是在其他所有内容后面设置一个不可见的全屏按钮,并在按下它时采取适当的行动。
【讨论】:
这听起来是个糟糕的主意。【参考方案4】:Ben 的答案略有不同,处理延伸到父母之外的孩子。 如果 clipChildren 为 YES,那么对于在主控件之外但在某个子控件内的点,这将不会返回 YES。 如果 clipChildren 为 NO,这与 Ben 的相同。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
BOOL clipChildren = YES;
if (!clipChildren || [super pointInside:point withEvent:event])
for (UIView * view in [self subviews])
if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
return YES;
return NO;
【讨论】:
以上是关于禁用对 UIView 背景的触摸,以便下部视图上的按钮可点击的主要内容,如果未能解决你的问题,请参考以下文章
UIView clipToBounds 没有停止子视图接收父视图之外的触摸