防止 child 视图控制器上未处理的触摸事件传递到容器视图

Preventing unhandled touch events on a child view controller from passing through to container view

我有一个容器视图控制器管理它自己的 full-screen 内容视图,附加了几个手势识别器。 child 视图控制器可以覆盖屏幕的一部分;它的根视图是一个提供不透明背景颜色的 UIView,它被一个 UIScrollView 覆盖,而 UIScrollView 又包含堆栈视图等的复杂视图层次结构。

在 child 中滚动工作正常,任何用户与其子视图的交互也是如此。我遇到的问题是滚动视图本身(即不在其任何子视图内)上的任何点击或其他 non-scrolling 手势都会穿过它后面的空 UIView 并且意外地被手势识别器处理parent(容器)控制器的根视图。我希望这些触摸被 child 的背景视图吞没,这样它们就是 ignored/cancelled.

我的第一个想法是将 child VC 上的 nextResponder 覆盖为 return 零,假设这样可以防止触摸事件传递到超级视图。那里没有成功,所以我尝试覆盖 child 控制器上的触摸处理方法(touchesBegan: 等),但它们从未被调用过。然后我将一个简单的 UIView 子类替换为我的 child 控制器的根视图,同样尝试了这两种方法。同样 returnnextResponder 为 nil 无效,触摸方法永远不会被调用。

我的响应链看起来完全按照我的预期设置:滚动视图 --> child VC 的根视图 --> child VC --> parent 的根视图 --> parent VC。这让我觉得我的控制器容器设置正确,并且让我怀疑 parent 的根视图上的手势识别器以某种我不理解的方式在响应者链中胜出。

这看起来应该很容易。我错过了什么?谢谢!

多亏了 this very helpful WWDC video,我想我更了解这里发生的事情了。

给定传入触摸,系统首先将该触摸与最深的命中测试视图相关联;在我的例子中是 UIScrollView。然后它显然会回到超级视图的层次结构中寻找任何其他附加的识别器。文档的这个关键位暗示了这种行为:

A gesture recognizer operates on touches hit-tested to a specific view and all of that view’s subviews.

滚动视图有自己的内部平移识别器,它可以取消无法识别的触摸,或者可能回退到不会碰巧转发响应链的响应方法。这解释了为什么我的响应器方法永远不会被调用,即使我自己的识别器被禁用。

有了这些信息,我可以想出几种可能的方法来解决我的问题,例如:

  • 使用手势委托方法忽略触摸if/when关联视图在子控制器下。
  • 编写一个 "null" 捕获所有触摸并忽略它们的手势识别器子类,并将其附加到子控制器的根视图。

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

因此视图层次结构由此发生变化:

对此:

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