在UIScrollView中添加垂直双指滑动手势

4
根据如何为iPhone应用程序添加垂直滑动手势,我在window中添加了一个双指向下滑动手势,在整个应用程序的普通页面上都可以正常工作。但是在具有UIScrollView(如UITableViewController)的页面上失败了。当我在UIScrollView用两个手指向下滑动时,它只会像正常情况下一样滚动。如果我从位于UIScrollView上方的UINavigationBar向下滑动,则再次正常工作。

理想情况是,我可以通过一个手指正常地滚动tableview,并通过用两个手指滑动页面而不滚动tableview来调用某些方法。这在TweetBot中用于切换暗模式,非常完美。

根据苹果文档:使用响应器和响应者链处理事件,我认为我理解了响应者链的工作原理,所以我想要请求UIScrollView忽略双指滑动手势,以便将此事件传递到UIWindow。但是我无法弄清如何实现:

我尝试了从苹果的文档中实现UIGestureRecognizerDelegatefunc gestureRecognizer(_: shouldRequireFailureOf otherGestureRecognizer:),或者通过继承UITableView覆盖gestureRecognizerShouldBegin(_)。但是都没有起作用。

欢迎任何解决方案或建议。

更新-最终解决方案

我简化了joern的解决方案,下面是解决方案。

AppDelegate

// It's not necessary to keep a reference to gesture unless you want to do something further.
lazy var gesture: UIPanGestureRecognizer = {
    let gesture = UIPanGestureRecognizer(target: self, action: #selector(twoFingerDidSwipe(recognizer:)))
    gesture.minimumNumberOfTouches = 2
    gesture.maximumNumberOfTouches = 2
    return gesture
}()

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    gesture.delegate = AppDelegate
    window.addGestureRecognizer(gesture)
    return true
}

@objc func twoFingerDidSwipe(recognizer: UIPanGestureRecognizer) {
    let swipeThreshold: CGFloat = 50

    if recognizer.state == .changed { // 1
      switch recognizer.translation(in: window).y { // 2
      case ...(-swipeThreshold):
        print("Swipe Up")
        recognizer.state = .cancelled // 3
      case swipeThreshold...:
        print("Swipe Down")
        recognizer.state = .cancelled
      default:
        break
      }
    }
}

extension AppDelegate: UIGestureRecognizerDelegate {
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
  }
}

关于与joern的解决方案的不同之处的一些解释:

  1. 只有 recognizer.state == .change 有关。
  2. recognizer.translation(in:) 的参数应该是 window,这样我就无法对特定的 VC 做任何事情了。
  3. 在通过此识别器触发某些方法后,应该取消它以防止它们不断触发。

而且,没有必要对任何特定的 VC 做任何事情。

有了这个,两指滑动手势在普通 VC 和滚动 VC 上都可以正常工作。


请查看此链接:https://dev59.com/Nl7Va4cB1Zd3GeqPNty6 - Mahesh Shahane
1个回答

3

当你将UISwipeGestureRecognizer添加到你的窗口时,保留一个引用,这样你就可以通过AppDelegate稍后访问它:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var twoFingerSwipeDownRecognizer: UISwipeGestureRecognizer?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        let twoFingerSwipeDownRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(didRecognizeTwoFingerSwipeDown))
        twoFingerSwipeDownRecognizer.numberOfTouchesRequired = 2
        twoFingerSwipeDownRecognizer.direction = .down
        window?.addGestureRecognizer(twoFingerSwipeDownRecognizer)
        self.twoFingerSwipeDownRecognizer = twoFingerSwipeDownRecognizer

        return true
    }

    @objc func didRecognizeTwoFingerSwipeDown(recognizer: UISwipeGestureRecognizer) {
        print("SWIPE DOWN")
    }
}

然后,在包含UITableView(或UIScrollView)的UIViewController中,您需要在UITableView的拖动手势识别器上调用require(toFail:):
func enableTwoFingerSlideDown() {
    guard
        let appDelegate = UIApplication.shared.delegate as? AppDelegate,
        let twoFingerGestureRecognizer = appDelegate.twoFingerSwipeDownRecognizer
        else {
            return
        }
    tableView.panGestureRecognizer.require(toFail: twoFingerGestureRecognizer)
}

现在,在UITableView上可以使用双指下滑手势。 更新 由于滑动手势是一种离散的手势,所以上述解决方案可能不是最完美的解决方案。当您用一个手指缓慢向下滚动时,您会注意到UITableView不会立即向下滚动。有一个短暂的延迟,因为UITableViewUIPanGestureDelagate必须等待 (Two Finger) SwipeDelegate 失败。这需要一些时间。
更好的解决方案可能是使用UIPanGestureRecognizer来识别双指手势,然后在用户使用两个手指移动时禁用UITableView的滚动。
可以像这样实现: 在您的AppDelegate中:
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var twoFingerPanRecognizer: UIPanGestureRecognizer?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        let twoFingerSwipeDownRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didRecognizeTwoFingerPan))
        twoFingerSwipeDownRecognizer.minimumNumberOfTouches = 2
        twoFingerSwipeDownRecognizer.maximumNumberOfTouches = 2
        twoFingerPanRecognizer?.delegate = self
        window?.addGestureRecognizer(twoFingerSwipeDownRecognizer)
        self.twoFingerPanRecognizer = twoFingerSwipeDownRecognizer

        return true
    }

    @objc func didRecognizeTwoFingerPan(recognizer: UIPanGestureRecognizer) {
        let tableView = recognizer.view as? UITableView
        switch recognizer.state {
        case .began:
            tableView?.isScrollEnabled = false
        case .changed:
            let swipeThreshold: CGFloat = 50
            switch recognizer.translation(in: nil).y {
            case ...(-swipeThreshold):
                print("Swipe UP")
                recognizer.isEnabled = false
            case swipeThreshold...:
                print("Swipe DOWN")
                recognizer.isEnabled = false
            default:
                break
            }
        case .cancelled, .ended, .failed, .possible:
            tableView?.isScrollEnabled = true
            recognizer.isEnabled = true
        }
    }
}

extension AppDelegate: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

在您的ViewController中:
func enableTwoFingerSlideDown() {
        guard
            let appDelegate = UIApplication.shared.delegate as? AppDelegate,
            let twoFingerGestureRecognizer = appDelegate.twoFingerPanRecognizer
            else {
                return
            }
        tableView.addGestureRecognizer(twoFingerGestureRecognizer)
    }

你需要从AppDelegate中获取UIPanGestureRecognizer,并将其添加到UITableView中。否则这将不起作用。只需记住在此UIViewController被解除时将其重新添加回UIWindow即可。
通过这种解决方案,UITableView的正常滚动行为保持不变。

它有效。关键在于 require(toFail:)。当苹果在文档中提到“shouldRequireFailure”时,它只提到了UIGestureRecognizerDelegate的方法,而没有提到UITableView识别器的方法。我阅读了许多文章,没有人提到识别器的方法本身...非常感谢! - ribilynn
我刚刚发现UIScrollView通过panGestureRecognizer属性公开了它的UIPanGestureRecognizer。因此,您不必遍历所有手势识别器,而是可以直接在表视图的平移手势识别器上调用require(toFail:)。我已经更新了答案。 - joern
我必须说这个更新太棒了。但是在没有scrollView的普通页面上,即使这个页面是我的应用程序的初始ViewController(所以不涉及“将其添加回UIWindow”),这个panGesture也不起作用。我测试并发现,在普通的viewController上,当我用两个手指滑动时,didRecognizeTwoFingerPan会被调用,但永远不会到达case ...(-swipeThreshold):,总是到default: break。虽然如此,我仍然可以将旧的UISwipeGestureRecognizer保留到window中来解决它。 - ribilynn
1
我上传最终解决方案。 - ribilynn

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