同一视图上的多个手势(将视图拖出可滚动视图)无法正常工作?

Multiple gestures on same view (drag view out of scrollable view) not working the right way?

为了让我的 panGesture(附加到 UIImageView)被调用,我需要确保用户没有在图像所在的水平 ScrollView 中滚动。我是这样实现的:

let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
    panGesture.require(toFail: self.scrollView.panGestureRecognizer)

这很好用,但由于 scrollView水平,它只允许 panGesture 水平工作。我也想垂直拉动 UIImage,但没有任何反应,因为很明显,它只会在滚动视图失败时调用(只能水平失败)。

有没有一种方法可以确保上面的方法有效,而且无论如何都允许panGesture垂直调用?

好的,我结合了一些东西解决了这个问题。看起来要加的东西很多,不过在其他项目中会派上用场的。

第 1 步:启用水平滚动 UIScrollView 到 "fail" 水平 垂直。

虽然我只希望我的 UIScrollView 水平滚动,但我仍然需要它垂直滚动以便它可以 失败 (解释来了)。失败使我能够 "pull" UIScrollView.

的子视图 out

scrollView本身的高度是40所以它的contentSize必须有更大的高度才能勉强[=128] =] 垂直滚动。

self.effectTotalHeight.constant = 41
self.scrollView.contentSize = CGSize(width: contentWidth, height: self.effectTotalHeight.constant)

太棒了!它垂直滚动。但现在内容橡皮筋(我们不想要)。反对 SO 上的其他人所说的,不要便宜,只是禁用弹跳。 (特别是如果你想要水平滚动时的弹跳!)

注意:我还意识到仅在 StoryBoard 中禁用 Bounce Horizontally 确实...好吧,什么都没有(错误?)。

第 2 步: 添加 UIScrollViewDelegate 到您的视图控制器 class 并检测滚动条

现在我想检测滚动并确保在垂直滚动时,它不会实际滚动。为此,contentOffset.y 位置不应更改,即使您正在滚动。 UIScrollViewDelegate 提供了一个在滚动时调用的委托函数 scrollViewDidScroll。只需将其设置为:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    scrollView.contentOffset.y = 0.0
}

为了调用它,您还需要将 scrollView 的委托设置为 self

self.scrollView.delegate = self

记住这个控件 all UIScrollViews 所以提供一个 if 语句或 switch 语句如果你想让它只影响具体的。在我的例子中,这个视图控制器只有一个 UIScrollView 所以我没有放任何东西。

耶!所以现在它只会再次水平滚动,但这种方法只会将 contentOffset.y 保持在 0。它不会使其失败。我们确实需要它,因为 scrollView 垂直失败是启用平移手势识别器的关键(可以让您拉动和拖动等)。所以让我们让它失败吧!

步骤 3: 覆盖 UIScrollView 默认手势识别

为了覆盖任何默认手势识别器,我们需要将 UIGestureRecognizerDelegate 作为另一个委托方法添加到您的视图控制器 class。

scrollView 现在需要自己的平移手势识别器处理程序,以便我们可以检测其上的手势。您还需要将新 scrollGesture 的委托设置为 self 以便我们可以检测到它:

let scrollGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handleScroll(_:)))
    scrollGesture.delegate = self
self.scrollView.addGestureRecognizer(scrollGesture)

设置handleScroll函数:

func handleScroll(_ recognizer: UIPanGestureRecognizer) {
    // code here
}

一切都很好,但为什么我们要设置这一切?请记住,我们禁用了 contentOffset.y 但垂直滚动并未 失败 。现在我们需要检测滚动的方向,如果垂直则让它失败,这样 panHandle 可以被激活。

第四步:检测手势方向并让它失败(失败是好的!)

我扩展了 UIPanGestureRecognizer 以便它可以通过进行以下 public 扩展来检测和发出方向:

public enum Direction: Int {
   case Up
   case Down
   case Left
   case Right

   public var isX: Bool { return self == .Left || self == .Right }
   public var isY: Bool { return !isX }
}

public extension UIPanGestureRecognizer {
  public var direction: Direction? {
      let velo = velocity(in: view)
      let vertical = fabs(velo.y) > fabs(velo.x)
      switch (vertical, velo.x, velo.y) {
         case (true, _, let y) where y < 0: return .Up
         case (true, _, let y) where y > 0: return .Down
         case (false, let x, _) where x > 0: return .Right
         case (false, let x, _) where x < 0: return .Left
         default: return nil
      }
   }
}

现在为了正确使用它,您可以在 handleScroll 函数中获取 recognizer.direction 并检测发出的方向。在这种情况下,我正在寻找 .Up.Down,并且我希望它 发出失败 。我们通过禁用识别器来做到这一点,但随后立即重新启用它:

func handleScroll(_ recognizer: UIPanGestureRecognizer) {
    if (recognizer.direction == .Up || recognizer.direction == .Down) {
        recognizer.isEnabled = false
        recognizer.isEnabled = true
    }
}

false 之后立即将 .isEnabled 设置为 true 的原因是因为 false 发出失败 ,启用另一个手势("pulling out"(平移)其内部视图),但不会永远禁用(否则它将停止被调用)。通过将其设置回 true,它可以在发出失败后立即重新启用此侦听器。

第 5 步:让多个手势通过相互覆盖来工作

最后但同样重要的是,这是非常非常重要的一步,因为它允许平移手势和滚动手势独立工作,而不是让一个总是覆盖另一个。

func gestureRecognizer(_: UIGestureRecognizer,
    shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
    return true
}

就是这样!这是对 SO 的大量研究(主要是发现什么不起作用)和实验,但它们都结合在一起。

写这篇文章的目的是希望您已经编写了您所在的滚动视图内对象的平移手势识别器 "pulling out" 以及如何处理其状态(.ended.changed 等)。我建议如果您需要帮助,请搜索 SO。有很多答案。

如果您对此回答有任何其他问题,请告诉我。