为了澄清,这是发生了什么(根据@switz):
- 响应
-touchesBegan:withEvent:,呈现一个视图控制器
模态地通过segue
- 当用户抬起手指时,应关闭视图控制器。
问题是如何对抬起的手指做出反应,因为
-touchesEnded:withEvent: 未被调用。
简短的回答是呈现的视图控制器需要使用“Over Full
屏幕”modalPresentationStyle 而不是默认的“全屏”样式
(这可以指定为 segue 的表示样式,或者如果
那是“默认”,然后是呈现的视图控制器的呈现风格)。
长答案需要简要概述触摸处理的工作原理。这
解释忽略了手势识别器:
当触摸开始时,它被传递到包含
接触点。从那里它沿着响应者链传递,直到一些
对象决定处理触摸(通过实现
-touchesBegan:withEvent: 而不是调用 super)。
对触摸的后续更改(例如,移动、结束、取消)会返回
到接受触摸的相同视图。该视图将继续收到
触摸事件,直到触摸完成或取消。
当应用程序移动到后台时,触摸被取消(因为
例如有电话打进来),或者当像 UIScrollView 这样的 UIKit 类决定时
它需要接管触摸处理(因为手指移动得足够远
看起来用户想要滚动)。这里还有一些有趣的东西
UIScrollView.delaysContentTouches,但可以忽略。
但是有一个问题,没有记录在案:仅触摸交付
只要视图保持与窗口相关联,就会发生。如果认为
被认为是“最顶层”(与UITouch 关联的视图)是
从窗口中移除,则认为触摸已消失,并且,
重要的是,该触摸的任何事件都不会再次传递给任何人。这是
即使有问题的视图不是对象处理触摸也是如此。
最后的皱纹是造成这个问题的原因。因为默认
“全屏”演示样式实际上删除旧视图控制器的
从窗口查看,触摸处理立即停止。然而,“过
全屏”演示风格并没有删除它,它只是掩盖了旧的
查看与一个。 “Over Full Screen”通常在呈现视图时使用
控制器不是完全不透明的,但在这种情况下,我们正在使用它,所以触摸
处理不会中断。
但这还不是全部。这里还有一个问题,就是当视图
被触摸住在 UIScrollView 内(一个可以滚动或
总是反弹)。在这种情况下,即使使用“全屏”,您也会发现,
在继续传递触摸事件的同时,稍微移动手指
突然导致触摸被取消。这是因为UIScrollView
不知道它被掩盖了,并决定用户实际上是在尝试
滚动。这会导致它取消触摸。
不过,有一个解决方案。这有点难看,但解决方案是
执行时立即取消任何封闭滚动视图上的任何滚动
转场。这可以通过如下代码来完成:
class ViewController: UIViewController {
// this is called from -touchesBegan:withEvent: from a child view
// the child view is `sender`
func touchDown(sender: UIView) {
var view = sender.superview
while view != nil {
if let scrollView = view as? UIScrollView {
// toggle the panGestureRecognizer enabled state to immediately
// cause it to fail.
let enabled = scrollView.panGestureRecognizer.enabled
scrollView.panGestureRecognizer.enabled = true
scrollView.panGestureRecognizer.enabled = enabled
}
view = view?.superview
}
performSegueWithIdentifier(identifier, sender: self)
}
// ...
}
当然,没有手势就没有关于触摸处理的讨论是完整的
识别器。手势识别器几乎改变了触摸的一切
处理。他们在任何触摸时都会获得第一手,并且可以中断视图触摸
随时处理。例如,UIScrollView 的 UIPanGestureRecognizer
是用于滚动的,当它进入“开始”状态时(因为
用户已经移动了足够多的手指),这就是导致触摸的原因
取消。
因此,考虑到这一点,最好的解决方案是不实施
-touchesBegan:withEvent: 完全没有,但要使用手势识别器。最简单的
这里的解决方案是使用UILongPressGestureRecognizer
minimumPressDuration 设置为 0 和 allowableMovement 设置为一些
高得离谱的价值(因为你不想移动来取消触摸)。我是
推荐这个是因为UILongPressGestureRecognizer 是一个连续的
识别器,这意味着它将发送 Began、Moved 和 Ended 的事件,并带有
推荐的设置,它将响应触摸开始发送它们,
移动,结束。更重要的是,一旦你的识别器开始处理触摸,
这会自动阻止任何其他识别器(例如滚动视图的平移
识别器)从“接管”和取消触摸。
请注意,如果您将手势识别器附加到 scrollView 本身
(例如UITableView)但只想响应某些位置的触摸
(例如连续),那么您需要限制识别器。您可以使用
委托方法gestureRecognizer(_:shouldReceiveTouch:) 来做这件事
像这样:
func gestureRecognizer(recognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
// if you might be the delegate of multiple recognizers, check for that
// here. This code will assume `recognizer` is the correct recognizer.
// We're also assuming, for the purposes of this code, that we're a
// UITableViewController and want to only capture touches on rows in the
// first section.
let touchLocation = touch.locationInView(self.tableView)
if let indexPath = self.tableView.indexPathForRowAtPoint(touchLocation) {
if indexPath.section == 0 {
// we're on one of the special rows
return true
}
}
return false
}
这样识别器就不会阻止tableView的panGestureRecognizer
当用户触摸桌子上的其他地方时不会滚动。