防止未处理的触摸事件从子视图控制器传递到容器视图。

5
我有一个容器视图控制器,管理其自己的全屏内容视图,并附有多个手势识别器。子视图控制器可以覆盖部分屏幕;其根视图是提供不透明背景颜色的UIView,被一个UIScrollView覆盖,后者包含一个复杂的视图层次结构,包括堆叠视图等等。
在子级中滚动工作正确,以及与其子视图的任何用户交互。我遇到的问题是,滚动视图本身上的任何点按或其他非滚动手势(即不在其任何子视图内)都通过其后面的空UIView滑落,意外地由父(容器)控制器的根视图的手势识别器处理。 我希望这些触摸事件被子级的背景视图吞噬,以便它们被忽略/取消。
我的第一个想法是,在子VC上重写nextResponder,返回nil,假设这将防止触摸事件传递到superview。但没有成功,所以我尝试在子控制器上重写touch handling方法(touchesBegan:等)。但他们从未被调用。 然后我替换了一个简单的UIView子类来成为我的子控制器的根视图,同样地,在那里尝试了这两种方法。 再次返回nil以获取nextResponder没有效果,并且触摸方法从未被调用。
我的响应者链看起来设置得正好如我所期望的那样:滚动视图 -> 子VC的根视图 -> 子VC -> 父级的根视图 -> 父VC。这使我认为我的控制器容器设置正确,并让我怀疑父级根视图上的手势识别器在某种我不理解的方式上胜出响应者链。
这似乎应该很容易解决,我错过了什么吗?谢谢!

图形化展示您的项目将非常有帮助,或者演示项目:),或者几行代码。 - Jageen
谢谢@SandeepBhandari,我在使用普通的UIView和使用我的子类时都启用了用户交互。我尝试关闭它(认为这可能是防止触摸事件向上传递所需的),但这也会将其所有子视图(我的滚动视图等)的用户交互功能关闭。 - 4bar
@4bar:尝试按照我在答案中发布的方式实现hitTest :) 这应该会对你有所帮助。 - Sandeep Bhandari
@4bar:看起来很有趣。我会关注这个问题的 :) 你不能简单地在hit test中返回nil,这就是为什么我添加了条件并调用super.hitTest,但现在你说你的scrollView覆盖了整个视图并且hit test将始终成功,所以我删除了我的答案。 - Sandeep Bhandari
这里有个例子。假设我在我的滚动视图内向左滑动(该视图只能垂直滚动)。该触摸操作不会被滚动视图的内部平移手势识别器识别,因此被忽略并传递了上去。但是,它似乎没有被其父视图的touchesBegan方法捕获(即该视图的根视图),而是直接进入了其父视图的手势识别器。这就是我感到困惑的地方。我想我可以根据需要禁用那些识别器,但我真的想理解这个问题。 - 4bar
显示剩余3条评论
1个回答

4

由于这个非常有帮助的WWDC视频,我现在更好地理解了这里正在发生的事情。

给定一个传入的触摸,首先系统将该触摸与最深的命中测试视图相关联;在我的情况下,那是UIScrollView。然后它显然向上遍历父视图层次结构,寻找任何其他已连接的识别器。这种行为是由这个关键性的文档部分所暗示的:

手势识别器对特定视图及其所有子视图上的触摸进行操作。

滚动视图具有其自己的内部平移识别器,它可以取消未被识别的触摸,或者可能会回退到不恰好将触摸转发到响应者链的响应方法。这就解释了为什么即使我的识别器被禁用时,我的响应方法也从未被调用。

有了这些信息,我可以想到一些可能解决我的问题的方法,例如:

  • 使用手势委托方法,在关联视图位于子控制器下方时忽略触摸。
  • 编写一个“空”手势识别器子类,捕获并忽略所有触摸,并将其附加到子控制器的根视图上。

但是我最终做的只是重新排列我的视图层次结构,将一个新的空视图放在顶部,这样我的子控制器视图可以成为主内容视图的兄弟视图,而不是其子视图。

因此,视图层次结构从这个变成了:

"Before" hierarchy

转换为:

"After" hierarchy

这解决了我的问题:我的手势识别器不再与命中测试到子控制器视图的触摸交互。我认为这更好地捕捉了我的应用程序控制器之间的概念关系,而不需要任何额外的逻辑。

谢谢。我重新排列了视图,使其成为“顶部”视图的祖先,并且祖先现在可以获取未处理的手势。之前它只是祖先的一个子级,无法获取未处理的手势。 - William J Bagshaw
@4bar - 你的回答真是太棒了!这是一个非常出色的超越传统思维的例子,你应该得到一枚诺贝尔奖! - Maciek Czarnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接