Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 484 Vote(s) - 3.57 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How can I implement "drag right to dismiss" a View Controller that's in a navigation stack?

#1
By default, if you drag right from the left edge of the screen, it will drag away the ViewController and take it off the stack.

I want to extend this functionality to the entire screen. When the user drags right anywhere, I'd like the same to happen.

I know that I can implement a swipe right gesture and simply call ```self.navigationController?.popViewControllerAnimated(true)```

However, there is no "dragging" motion. I want the user to be able to right-drag the view controller as if it's an object, revealing what's underneath. And, if it's dragged past 50%, dismiss it. (Check out instagram to see what I mean.)
Reply

#2
The cleanest way is to subclass your navigation controller and add a directional pan gesture recognizer to its view that borrows its target/action properties from the default interaction pan gesture recognizer.

First, create a directional pan gesture recognizer that simply puts itself into a failed state if the initial gesture is not in the desired direction.

class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
enum Direction {
case up
case down
case left
case right
}

private var firstTouch: CGPoint?
var direction: Direction

init(direction: Direction, target: Any? = nil, action: Selector? = nil) {
self.direction = direction
super.init(target: target, action: action)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
firstTouch = touches.first?.location(in: view)
super.touchesBegan(touches, with: event)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
switch state {
case .possible:
if let firstTouch = firstTouch,
let thisTouch = touches.first?.location(in: view) {
let deltaX = thisTouch.x - firstTouch.x
let deltaY = thisTouch.y - firstTouch.y

switch direction {
case .up:
if abs(deltaY) > abs(deltaX),
deltaY < 0 {
break
} else {
state = .failed
}

case .down:
if abs(deltaY) > abs(deltaX),
deltaY > 0 {
break
} else {
state = .failed
}

case .left:
if abs(deltaX) > abs(deltaY),
deltaX < 0 {
break
} else {
state = .failed
}

case .right:
if abs(deltaX) > abs(deltaY),
deltaX > 0 {
break
} else {
state = .failed
}
}
}
default:
break
}
super.touchesMoved(touches, with: event)
}

override func reset() {
firstTouch = nil
super.reset()
}
}

Then subclass `UINavigationController` and perform all of the logic in there.

class CustomNavigationController: UINavigationController {
let popGestureRecognizer = DirectionalPanGestureRecognizer(direction: .right)

override func viewDidLoad() {
super.viewDidLoad()
replaceInteractivePopGestureRecognizer()
}

private func replaceInteractivePopGestureRecognizer() {
guard let targets = interactivePopGestureRecognizer?.value(forKey: "targets") else {
return
}
popGestureRecognizer.setValue(targets, forKey: "targets")
popGestureRecognizer.delegate = self
view.addGestureRecognizer(popGestureRecognizer)
interactivePopGestureRecognizer?.isEnabled = false // this is optional; it just disables the default recognizer
}
}

And then conform to the delegate. We only need the first method, `gestureRecognizerShouldBegin`. The other two methods are optional.

Most apps that have this feature enabled won't work if the user is in a scroll view and it's still scrolling; the scroll view must come to a complete stop before the swipe-to-pop gesture is recognized. This is not how it works with the default recognizer so the last two methods of this delegate (1) allow simultaneous gesturing with scroll views but (2) force the pop recognizer to fail when competing with the scroll view.

// MARK: - Gesture recognizer delegate
extension CustomNavigationController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if otherGestureRecognizer.view is UIScrollView {
return true
}
return false
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if otherGestureRecognizer.view is UIScrollView {
return true
}
return false
}
}
Reply

#3
**Swipe Right to dismiss the View Controller**

Swift 5 Version -
(Also removed the gesture recognition when swiping from right - to - left)

**Important** -
In ‘Attributes inspector’ of VC2, set the ‘Presentation’ value from ‘Full Screen’ to ‘Over Full Screen’. This will allow VC1 to be visible during dismissing VC2 via gesture — without it, there will be black screen behind VC2 instead of 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)
})
}
}
}
}
```
Reply

#4
Answers are too complicated. There is a simple solution. Add next line to your base navigation controller, or navigation controller that you want to have this ability:


self.interactivePopGestureRecognizer?.delegate = nil
Reply

#5
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!

**Edit:** Updated the code with more detailed solution.

```swift
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)
}
}
}
}

```


```swift
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
}
}
}
}

```

In your collection view for example:

```swift
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
}
}
```
Reply

#6
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.

1. if you swipe left it also dismisses just as if you were swiping right.
2. 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
}
}
}
Reply

#7
I think this is easier than the suggested solution and also works for all viewControllers inside that navigation and also for nested scrollviews.

[To see links please register here]


Just install the pod and then use `EZNavigationController` instead of `UINavigationController` to have this behavior on all view controllers inside that navigation controller.
Reply

#8
You need to investigate the [interactivePopGestureRecognizer][1] property of your `UINavigationController`.

Here is a similar question with example code to hook this up.

[To see links please register here]


[1]:

[To see links please register here]

Reply

#9
[![enter image description here][1]][1]


Made a demo project in Github <br>

[To see links please register here]


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:

1. Calculate the percentage of drag on the view
2. `.Begin:` Specify which segue to perform and assign `UINavigationController` delegate. delegate will be needed for `InteractiveTransitioning`
3. `.Changed:` UpdateInteractiveTransition with percentage
4. `.Ended:` Continue remaining transitioning if drag 50% or more or higher velocity else cancel
5. `.Cancelled, .Failed:` cancel transitioning

<br>

References:

1. [UIPercentDrivenInteractiveTransition][2]
2.

[To see links please register here]

3.

[To see links please register here]

4.

[To see links please register here]



[1]:

[2]:

[To see links please register here]

Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through