iOS:检测触摸按下、segue和触摸抬起

5
  1. 用户将手指放在屏幕上。这将触发一个UITouchEvent,阶段为Began,调用controllerA中的touchesBegan:withEvent:方法,该方法执行从controllerAcontrollerB的segue。
  2. 用户将手指从屏幕上拿起。这将触发一个UITouchEvent,阶段为Ended,调用某个回调方法。

问题:这个回调方法是什么,位于哪里?它不在controllerA中,也不在controllerB中。据我所知,它不在任何视图中。但它确实存在。


你能否在VC A中进行touches released的NSLOG吗?我很确定那个会被调用。 - DogCoffee
不,正如我之前提到的那样,在任何控制器中都没有调用它。 - Tsubaki
你想做什么?当执行segue时,从一个视图控制器传递参数到另一个视图控制器吗? - Stefan
增加了一份澄清。 - Tsubaki
有什么想法吗?这似乎是一个非常普遍的问题。 - Tsubaki
显示剩余2条评论
1个回答

3
为了澄清,以下是@switz提供的情况:
- 响应-touchesBegan:withEvent:后,通过segue以模态方式呈现视图控制器。 - 当用户松开手指时,应该关闭视图控制器。
问题在于如何对抬起手指作出反应,因为不会调用-touchesEnded:withEvent:
简短的答案是,呈现的视图控制器需要使用“全屏幕”modalPresentationStyle而不是默认的“全屏幕”样式(这可以作为segue的呈现样式指定,或者如果是“Default”,则作为呈现的视图控制器的呈现样式)。
长答案需要简要概述触摸处理的工作原理。此解释忽略手势识别器:
当触摸开始时,它被传递到包含触摸点的“最上层”视图。从那里,它沿着响应者链传递,直到某个对象决定处理触摸(这是通过实现-touchesBegan:withEvent:并不调用super来表示的)。
随后对触摸的更改(例如移动、结束、取消)将返回相同的接受触摸的视图。视图将继续接收触摸事件,直到触摸结束或取消。
当应用程序进入后台(例如因为电话来了)或者UIScrollView之类的UIKit类决定需要接管触摸处理时(因为手指移动得足够远,看起来像是用户想要滚动),触摸被取消。这里还有一些与UIScrollView.delaysContentTouches相关的有趣操作,但可以忽略它们。
但是有一个细节,即没有记录下来:只要视图仍然与窗口关联,就会发生触摸传递。如果被认为是“最上层”的视图(与UITouch相关联的视图)从窗口中移除,则认为触摸已消失,并且重要的是,不会再向任何人传递该触摸的事件。即使涉及的视图不是处理触摸的对象,这也是正确的。
而这个最终的细节就是这个问题的原因。因为默认的“全屏幕”呈现样式实际上从窗口中删除了旧的视图控制器的视图,所以触摸处理立即停止。但是,“Over Full Screen”呈现样式不会删除它,它只是用一个视图覆盖旧的视图。“Over Full Screen”通常用于呈现的视图控制器不是完全不透明的情况,但在这种情况下,我们使用它来避免中断触摸处理。
但这还不是全部。还有另一个问题,当被触摸的视图位于一个UIScrollView内部时(无论它是可滚动的还是总是弹跳),即使启用了 "Over Full Screen",你会发现,在移动手指时,稍微动一下手指就会导致触摸被取消。这是因为UIScrollView并不知道它已经被覆盖,并且认为用户实际上是想滚动。这会导致它取消触摸。
不过,这个问题也有解决方案。虽然有点丑陋,但解决方案是在执行segue时立即取消任何包含滚动视图上的滚动。可以使用以下代码来完成这个操作:
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用于滚动,当它进入“began”状态(因为用户移动了足够的手指),这就会导致触摸被取消。因此,在这种情况下,最好的解决方案是根本不实现-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滚动。

提供信息 - 我已将长按最短持续时间修改为 0.1,并检查了 if (recognizer != yourLongPressGestureRecognizer) { return true }。 这使得用户可以轻松滚动,同时仍允许无缝的“按住并保持”操作。 此外,如果您有一个附加按钮,您可以检测是否在最右边进行了按压。 if fingerLocation.x > self.tableView.bounds.size.width - 40 { // accessory tap } - switz

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