iOS:检测触摸,触发,修饰

Tsu*_*aki 5 iphone objective-c ios

  1. 用户将手指放在屏幕上.这会触发UITouchEvent,相位Began,它调用touchesBegan:withEvent:在方法controllerA,其中从执行SEGUE controllerAcontrollerB.
  2. 用户将手指从屏幕上抬起.这会触发UITouchEvent,相位Ended,它调用一些回调方法.

问题:此回调方法的内容和位置是什么?它不在controllerA,它不在controllerB.据我所知,这不是任何观点.但它存在.

Lil*_*ard 3

澄清一下,这是发生的事情(根据@switz):

  • 作为响应-touchesBegan:withEvent:,视图控制器通过 segue 以模态方式呈现
  • 当用户抬起手指时,视图控制器应该被关闭。

问题是如何对抬起的手指做出反应,因为 -touchesEnded:withEvent:没有被调用。

简短的答案是所呈现的视图控制器需要使用“全屏”modalPresentationStyle而不是默认的“全屏”样式(这可以指定为 segue 的呈现样式,或者如果是“默认”则呈现样式所呈现的视图控制器)。

长答案需要简要概述触摸处理的工作原理。这个解释忽略了手势识别器:

当触摸开始时,它会被传递到包含触摸点的“最顶层”视图。从那里它沿着响应者链传递,直到某个对象决定处理触摸(这通过实现 -touchesBegan:withEvent:而不是调用来表示super)。

对触摸的后续更改(例如移动、结束、取消)将传送回接受触摸的同一视图。视图将继续接收触摸事件,直到触摸完成或取消。

当应用程序移动到后台时(例如,因为有电话打进来),或者当 UIKit 类UIScrollView决定它需要接管触摸处理时(因为手指移动得足够远,看起来像用户),触摸就会被取消。想要滚动)。这里还有一些有趣的东西UIScrollView.delaysContentTouches,但可以忽略。

但有一个问题,没有记录:触摸传递只有在视图与窗口保持关联时才会发生。如果被视为“最顶层”的视图(与 关联的视图)UITouch从窗口中删除,则触摸被视为已消失,重要的是,不会再次向任何人传递该触摸的事件。即使相关视图不是处理对象的对象,情况也是如此。

最后的皱纹就是这个问题的原因。因为默认的“全屏”呈现样式实际上从窗口中删除了旧视图控制器的视图,所以触摸处理立即停止。然而,“全屏”的呈现方式并没有消除它,它只是用旧视图掩盖了旧视图。当呈现的视图控制器不是完全不透明时,通常使用“全屏”,但在这种情况下,我们使用它,以便触摸处理不会中断。


但这还不是全部。这里还有另一个问题,那就是当被触摸的视图位于 a 内部时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)
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

当然,如果没有手势识别器,对触摸处理的讨论就不完整。手势识别器几乎改变了触摸处理的一切。他们对任何触摸都有优先权,并且可以随时中断视图触摸处理。例如,UIScrollView'sUIPanGestureRecognizer 用于滚动,当它进入“开始”状态时(因为用户已经移动了足够的手指),这就是导致触摸被取消的原因。

因此,考虑到这一点,实际上最好的解决方案是根本不实现 -touchesBegan:withEvent:,而是使用手势识别器。这里最简单的解决方案是使用UILongPressGestureRecognizerwith minimumPressDurationset to0并将allowableMovement其设置为一些高得离谱的值(因为您不希望移动取消触摸)。我推荐这个,因为UILongPressGestureRecognizer它是一个连续识别器,这意味着它将发送开始、移动和结束的事件,并且使用推荐的设置,它将发送它们以响应触摸开始、移动和结束。此外,一旦您的识别器开始处理触摸,就会自动阻止任何其他识别器(例如滚动视图的平移识别器)“接管”并取消触摸。


请注意,如果您将手势识别器附加到滚动视图本身(例如 a 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
}
Run Code Online (Sandbox Code Playgroud)

这样, 当用户触摸桌子上的其他位置时,识别器就不会阻止 滚动tableViewpanGestureRecognizer