How can I implement "drag right to dismiss" a View Controller that's in a navigation stack?
Made a demo project in Github
https://github.com/rishi420/SwipeRightToPopController
I've used UIViewControllerAnimatedTransitioning
protocol
From the doc:
// This is used for percent driven interactive transitions, as well as for container controllers ...
Added a UIPanGestureRecognizer
to the controller's view. This is the action of the gesture:
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
}
}
Steps:
- Calculate the percentage of drag on the view
.Begin:
Specify which segue to perform and assignUINavigationController
delegate. delegate will be needed forInteractiveTransitioning
.Changed:
UpdateInteractiveTransition with percentage.Ended:
Continue remaining transitioning if drag 50% or more or higher velocity else cancel.Cancelled, .Failed:
cancel transitioning
References:
- UIPercentDrivenInteractiveTransition
- https://github.com/visnup/swipe-left
- https://github.com/robertmryan/ScreenEdgeGestureNavigationController
- https://github.com/groomsy/custom-navigation-animation-transition-demo
Create a pan gesture recogniser and move the interactive pop gesture recogniser's targets across.
Add your recogniser to the pushed view controller's viewDidLoad and voila!
let popGestureRecognizer = self.navigationController!.interactivePopGestureRecognizer!
if let targets = popGestureRecognizer.value(forKey: "targets") as? NSMutableArray {
let gestureRecognizer = UIPanGestureRecognizer()
gestureRecognizer.setValue(targets, forKey: "targets")
self.view.addGestureRecognizer(gestureRecognizer)
}
Swift 4 version of the accepted answer by @Warif Akhand Rishi
Even though this answer does work there are 2 quirks that I found out about it.
- if you swipe left it also dismisses just as if you were swiping right.
- it's also very delicate because if even a slight swipe is directed in either direction it will dismiss the vc.
Other then that it definitely works and you can swipe either right or left to dismiss.
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
}
}
}