如何实现"向右拖动以关闭"导航堆栈中的视图控制器?

TIM*_*MEX 16 ios swift

默认情况下,如果从屏幕的左边缘向右拖动,它将拖离ViewController并将其从堆栈中取出.

我想将此功能扩展到整个屏幕.当用户在任何地方拖动时,我也希望发生同样的事情.

我知道我可以实现向右滑动手势并简单地调用 self.navigationController?.popViewControllerAnimated(true)

但是,没有"拖动"动作.我希望用户能够右键拖动视图控制器,就好像它是一个对象,揭示了底层的内容.并且,如果它被拖过50%,则将其解雇.(看看Instagram,看看我的意思.)

War*_*shi 28

在此输入图像描述

在Github制作了一个演示项目
https://github.com/rishi420/SwipeRightToPopController

我用过UIViewControllerAnimatedTransitioning协议

来自doc:

//这用于百分比驱动的交互式转换,以及容器控制器......

UIPanGestureRecognizer在控制器的视图中添加了一个.这是手势的动作:

func handlePanGesture(panGesture: UIPanGestureRecognizer) {

    let percent = max(panGesture.translationInView(view).x, 0) / view.frame.width

    switch panGesture.state {

    case .Began:
        navigationController?.delegate = self
        navigationController?.popViewControllerAnimated(true)

    case .Changed:
        percentDrivenInteractiveTransition.updateInteractiveTransition(percent)

    case .Ended:
        let velocity = panGesture.velocityInView(view).x

        // Continue if drag more than 50% of screen width or velocity is higher than 1000
        if percent > 0.5 || velocity > 1000 {
            percentDrivenInteractiveTransition.finishInteractiveTransition()
        } else {
            percentDrivenInteractiveTransition.cancelInteractiveTransition()
        }

    case .Cancelled, .Failed:
        percentDrivenInteractiveTransition.cancelInteractiveTransition()

    default:
        break
    }
}
Run Code Online (Sandbox Code Playgroud)

脚步:

  1. 计算视图上拖动的百分比
  2. .Begin:指定要执行的segue和分配UINavigationController委托.将需要代表InteractiveTransitioning
  3. .Changed: 具有百分比的UpdateInteractiveTransition
  4. .Ended: 如果拖动50%或更高或更高的速度,则继续保持转换
  5. .Cancelled, .Failed: 取消过渡


参考文献:

  1. UIPercentDrivenInteractiveTransition
  2. https://github.com/visnup/swipe-left
  3. https://github.com/robertmryan/ScreenEdgeGestureNavigationController
  4. https://github.com/groomsy/custom-navigation-animation-transition-demo


Kug*_*men 9

创建平移手势识别器并移动交互式弹出手势识别器的目标。

将您的识别器添加到推送的视图控制器的 viewDidLoad 中,瞧!

编辑:使用更详细的解决方案更新了代码。

import os
import UIKit

public extension UINavigationController {
  func fixInteractivePopGestureRecognizer(delegate: UIGestureRecognizerDelegate) {
    guard
      let popGestureRecognizer = interactivePopGestureRecognizer,
      let targets = popGestureRecognizer.value(forKey: "targets") as? NSMutableArray,
      let gestureRecognizers = view.gestureRecognizers,
      // swiftlint:disable empty_count
      targets.count > 0
    else { return }

    if viewControllers.count == 1 {
      for recognizer in gestureRecognizers where recognizer is PanDirectionGestureRecognizer {
        view.removeGestureRecognizer(recognizer)
        popGestureRecognizer.isEnabled = false
        recognizer.delegate = nil
      }
    } else {
      if gestureRecognizers.count == 1 {
        let gestureRecognizer = PanDirectionGestureRecognizer(axis: .horizontal, direction: .right)
        gestureRecognizer.cancelsTouchesInView = false
        gestureRecognizer.setValue(targets, forKey: "targets")
        gestureRecognizer.require(toFail: popGestureRecognizer)
        gestureRecognizer.delegate = delegate
        popGestureRecognizer.isEnabled = true

        view.addGestureRecognizer(gestureRecognizer)
      }
    }
  }
}

Run Code Online (Sandbox Code Playgroud)
public enum PanAxis {
  case vertical
  case horizontal
}

public enum PanDirection {
  case left
  case right
  case up
  case down
  case normal
}

public class PanDirectionGestureRecognizer: UIPanGestureRecognizer {
  let axis: PanAxis
  let direction: PanDirection

  public init(axis: PanAxis, direction: PanDirection = .normal, target: AnyObject? = nil, action: Selector? = nil) {
    self.axis = axis
    self.direction = direction
    super.init(target: target, action: action)
  }

  override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
    super.touchesMoved(touches, with: event)

    if state == .began {
      let vel = velocity(in: view)
      switch axis {
      case .horizontal where abs(vel.y) > abs(vel.x):
        state = .cancelled
      case .vertical where abs(vel.x) > abs(vel.y):
        state = .cancelled
      default:
        break
      }

      let isIncrement = axis == .horizontal ? vel.x > 0 : vel.y > 0

      switch direction {
      case .left where isIncrement:
        state = .cancelled
      case .right where !isIncrement:
        state = .cancelled
      case .up where isIncrement:
        state = .cancelled
      case .down where !isIncrement:
        state = .cancelled
      default:
        break
      }
    }
  }
}

Run Code Online (Sandbox Code Playgroud)

例如,在您的集合视图中:

  open override func didMove(toParent parent: UIViewController?) {
    navigationController?.fixInteractivePopGestureRecognizer(delegate: self)
  }

Run Code Online (Sandbox Code Playgroud)
// MARK: - UIGestureRecognizerDelegate
extension BaseCollection: UIGestureRecognizerDelegate {
  public func gestureRecognizer(
    _ gestureRecognizer: UIGestureRecognizer,
    shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer
  ) -> Bool {
    otherGestureRecognizer is PanDirectionGestureRecognizer
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我不太明白,但这实际上是有效的,不需要transitionDelegates等。 (4认同)
  • 这是最干净的答案。 (2认同)