如何实现“向右拖动以关闭”导航堆栈中的视图控制器?
Posted
技术标签:
【中文标题】如何实现“向右拖动以关闭”导航堆栈中的视图控制器?【英文标题】:How can I implement "drag right to dismiss" a View Controller that's in a navigation stack? 【发布时间】:2016-02-14 06:18:13 【问题描述】:默认情况下,如果你从屏幕的左边缘向右拖动,它会拖走 ViewController 并将其从堆栈中移除。
我想将此功能扩展到整个屏幕。当用户在任何地方拖动时,我希望发生同样的事情。
我知道我可以实现向右滑动手势,只需调用self.navigationController?.popViewControllerAnimated(true)
但是,没有“拖动”动作。我希望用户能够像对象一样右键拖动视图控制器,从而显示下面的内容。并且,如果它拖到 50% 以上,请忽略它。 (查看 Instagram 以了解我的意思。)
【问题讨论】:
我认为这对你有帮助developer.apple.com/library/ios/documentation/UIKit/Reference/… Instagram 是否将其排除在 33% 以上?我刚刚尝试过,但在我看来,有 50% 的人会忽略这个观点。 @Blaszard 刚刚更新。 如果没有赏金,我会因为过于宽泛而结束这个问题。有多个教程(Apple、博客)和多个 github 库。 这些库完全符合您的要求:fastred/SloppySwiper 和 jaredsinclair/JTSSloppySwiping 【参考方案1】:在 Github 上做了一个演示项目 https://github.com/rishi420/SwipeRightToPopController
我用过UIViewControllerAnimatedTransitioning
协议
来自文档:
// 这用于百分比驱动的交互式转换,以及容器控制器...
在控制器的视图中添加了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
步骤:
-
计算视图上的拖动百分比
.Begin:
指定要执行的 segue 并分配 UINavigationController
委托。 InteractiveTransitioning
需要委托
.Changed:
UpdateInteractiveTransition 与百分比
.Ended:
如果拖动 50% 或更多或更高的速度,则继续剩余过渡,否则取消
.Cancelled, .Failed:
取消过渡
参考文献:
-
UIPercentDrivenInteractiveTransition
https://github.com/visnup/swipe-left
https://github.com/robertmryan/ScreenEdgeGestureNavigationController
https://github.com/groomsy/custom-navigation-animation-transition-demo
【讨论】:
有什么方法可以做到这一点,而不必重新创建您知道的默认弹出过渡? 步骤 2case .Begin:
在其中指定 segue
。它可以是您选择的任何选择。
我不是这个意思。在您的示例项目中,您创建了一个模仿默认过渡动画的SlideAnimatedTransitioning
类,对吧?我想知道我是否可以简单地使用默认动画,而不必模仿它......
我如何使用这个例子来 SwipeLeftToPushController ?
这太过分了...您可以将操作复制到平移手势识别器。请参阅下面的答案。【参考方案2】:
创建一个平移手势识别器并移动交互式弹出手势识别器的目标。
将您的识别器添加到推送的视图控制器的 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)
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
例如在您的收藏视图中:
open override func didMove(toParent parent: UIViewController?)
navigationController?.fixInteractivePopGestureRecognizer(delegate: self)
// MARK: - UIGestureRecognizerDelegate
extension BaseCollection: UIGestureRecognizerDelegate
public func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer
) -> Bool
otherGestureRecognizer is PanDirectionGestureRecognizer
【讨论】:
我不太明白,但这确实有效,不需要transitionDelegates等。 这是最干净的答案。 这是私有 API? 非常感谢,它非常干净和简单。 @AndreyOshev 它没有使用任何私有 API【参考方案3】:@Warif Akhand Rishi 接受的答案的 Swift 4 版本
尽管这个答案确实有效,但我发现了 2 个怪癖。
-
如果您向左滑动,它也会消失,就像您向右滑动一样。
它也非常微妙,因为即使向任一方向轻轻滑动也会关闭 vc。
除此之外它确实有效,您可以向右或向左滑动以关闭。
class ViewController: UIGestureRecognizerDelegate, UINavigationControllerDelegate
override func viewDidLoad()
super.viewDidLoad()
navigationController?.interactivePopGestureRecognizer?.delegate = self
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
view.addGestureRecognizer(panGesture)
@objc func handlePanGesture(_ gesture: UIPanGestureRecognizer)
let interactiveTransition = UIPercentDrivenInteractiveTransition()
let percent = max(gesture.translation(in: view).x, 0) / view.frame.width
switch gesture.state
case .began:
navigationController?.delegate = self
// *** use this if the vc is PUSHED on the stack **
navigationController?.popViewController(animated: true)
// *** use this if the vc is PRESENTED **
//navigationController?.dismiss(animated: true, completion: nil)
case .changed:
interactiveTransition.update(percent)
case .ended:
let velocity = gesture.velocity(in: view).x
// Continue if drag more than 50% of screen width or velocity is higher than 1000
if percent > 0.5 || velocity > 1000
interactiveTransition.finish()
else
interactiveTransition.cancel()
case .cancelled, .failed:
interactiveTransition.cancel()
default:break
【讨论】:
您的解决方案正在快速弹出视图控制器 你好@LanceSamaria 有没有办法我可以实现这个来关闭呈现而不是推送的 UIViewController? @IsraelMeshileya 我没有尝试过,但可能会将 navigationController?.popViewController(animated(: true) 的行更改为 dismiss... b> 哦,一点也不。我仍在尝试其他一些调整,但一旦完成会通知您。 @IsraelMeshileya 我将项目添加到 GitHub。只需下载它,运行它,点击按钮,在 myVC 出现后,滑动关闭它就可以正常工作。左上角有一个cancelButton,不需要点击取消。在这里:github.com/lsamaria/SwipeToDismiss。如果它适合你,请告诉我【参考方案4】:您需要调查UINavigationController
的interactivePopGestureRecognizer 属性。
这是一个类似的问题,带有示例代码来解决这个问题。
UINavigationController interactivePopGestureRecognizer working abnormal in iOS7
【讨论】:
【参考方案5】:我认为这比建议的解决方案更容易,并且也适用于导航内的所有 viewController 以及嵌套的滚动视图。
https://***.com/a/58779146/8517882
只需安装 pod,然后使用 EZNavigationController
而不是 UINavigationController
即可在该导航控制器内的所有视图控制器上具有此行为。
【讨论】:
【参考方案6】:答案太复杂了。有一个简单的解决方案。将下一行添加到您想要拥有此功能的基本导航控制器或导航控制器:
self.interactivePopGestureRecognizer?.delegate = nil
【讨论】:
【参考方案7】:向右滑动关闭视图控制器
Swift 5 版本 - (也去掉了从右向左滑动时的手势识别)
重要 - 在 VC2 的“属性检查器”中,将“Presentation”值从“Full Screen”设置为“Over Full Screen”。这将允许 VC1 在通过手势关闭 VC2 时可见 — 没有它,VC2 后面会出现黑屏,而不是 VC1。
class ViewController: UIGestureRecognizerDelegate, UINavigationControllerDelegate
var initialTouchPoint: CGPoint = CGPoint(x: 0, y: 0)
override func viewDidLoad()
super.viewDidLoad()
navigationController?.interactivePopGestureRecognizer?.delegate = self
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
view.addGestureRecognizer(panGesture)
@objc func handlePanGesture(_ sender: UIPanGestureRecognizer)
let touchPoint = sender.location(in: self.view?.window)
let percent = max(sender.translation(in: view).x, 0) / view.frame.width
let velocity = sender.velocity(in: view).x
if sender.state == UIGestureRecognizer.State.began
initialTouchPoint = touchPoint
else if sender.state == UIGestureRecognizer.State.changed
if touchPoint.x - initialTouchPoint.x > 0
self.view.frame = CGRect(x: touchPoint.x - initialTouchPoint.x, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
else if sender.state == UIGestureRecognizer.State.ended || sender.state == UIGestureRecognizer.State.cancelled
if percent > 0.5 || velocity > 1000
navigationController?.popViewController(animated: true)
else
UIView.animate(withDuration: 0.3, animations:
self.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
)
【讨论】:
以上是关于如何实现“向右拖动以关闭”导航堆栈中的视图控制器?的主要内容,如果未能解决你的问题,请参考以下文章