How to programmatically send a pangesture in swift - ios

I have a view that has panGesture functionality, but I need to send a pan-gesture from one point to another programatically. Is there a way to do this in swift using an animation with a specific time interval? Here is my attempt at calling the pan gesture programmatically:
var upPanPoint = CGPoint(x: contentView.center.x, y: contentView.center.y + 500)
var upPan = panGestureRecognizer.setTranslation(upPanPoint, inView: self)
onSwipe(upPan)
here is the code that recognizes the pan gesture:
func onSwipe(panGestureRecognizer : UIPanGestureRecognizer!) {
let view = panGestureRecognizer.view!
print(view)
switch (panGestureRecognizer.state) {
case UIGestureRecognizerState.Began:
if (panGestureRecognizer.locationInView(view).y < view.center.y) {
self.viewState.rotationDirection = .RotationAwayFromCenter
} else {
self.viewState.rotationDirection = .RotationTowardsCenter
}
case UIGestureRecognizerState.Ended:
self.finalizePosition()
default:
let translation : CGPoint = panGestureRecognizer.translationInView(view)
view.center = self.viewState.originalCenter + translation
self.rotateForTranslation(translation, withRotationDirection: self.viewState.rotationDirection)
self.executeOnPanForTranslation(translation)
}
}

// The Pan Gesture
func createPanGestureRecognizer(targetView: UIImageView) {
var panGesture = UIPanGestureRecognizer(target: self, action:("handlePanGesture:"))
targetView.addGestureRecognizer(panGesture)
}
func handlePanGesture(panGesture: UIPanGestureRecognizer) {
// get translation
var translation = panGesture.translationInView(view)
panGesture.setTranslation(CGPointZero, inView: view)
println(translation)
// create a new Label and give it the parameters of the old one
var label = panGesture.view as UIImageView
label.center = CGPoint(x: label.center.x+translation.x, y: label.center.y+translation.y)
label.multipleTouchEnabled = true
label.userInteractionEnabled = true
if panGesture.state == UIGestureRecognizer.State.began {
// add something you want to happen when the Label Panning has started
}
if panGesture.state == UIGestureRecognizer.State.ended {
// add something you want to happen when the Label Panning has ended
}
if panGesture.state == UIGestureRecognizer.State.changed {
// add something you want to happen when the Label Panning has been change ( during the moving/panning )
} else {
// or something when its not moving
}
}

let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture))
self.imageView.addGestureRecognizer(panGesture)
#objc func panGesture(sender: UIPanGestureRecognizer){
let point = sender.location(in: view)
let panGesture = sender.view
panGesture?.center = point
print(point)
}

With Swift version 4.2 you can set pan gesture programmatically using below code:
let panGesture = UIPanGestureRecognizer(target: self, action:(#selector(self.handleGesture(_:))))
self.view.addGestureRecognizer(panGesture)
#objc func handleGesture(_ sender: UIPanGestureRecognizer) {
switch sender.state {
case .began:
case .changed:
case .cancelled:
case .ended:
default:
break
}
}

Related

Deselect UIView "button" when dragging finger off of it

I have a UIView that I added a UILongPressGestureRecognizer to so that I can handle clicks and have that UIView work like a button.
let longPressGtr = UILongPressGestureRecognizer(target: self, action:#selector(longPressSelector))
longPressGtr.minimumPressDuration = 0.1
myView.isUserInteractionEnabled = true
myView.addGestureRecognizer(longPressGtr)
#objc func longPressSelector(_ gestureRecognizer: UILongPressGestureRecognizer) {
if gestureRecognizer.state == .began {
myView.backgroundColor = UIColor.gray
} else if gestureRecognizer.state == .ended {
myView.backgroundColor = UIColor.blue // my button is blue
doSomething()
}
}
func doSomething() {
print("view was pressed")
}
This works, but the one thing that doesn't is when I press and hold on my UIView but drag my finger off the view, the "button" doesn't unselect. It still fires doSomething(). A regular UIButton will deselect the button and not fire it's onClick if you are holding down on it an drag your finger off the view.
How can I implement this functionality into my UIView?
Or is there a better way to make a UIView act like a button?
You need to check whether the gesture is inside the view.
#objc func longPresserDidFire(_ presser: UILongPressGestureRecognizer) {
let gestureIsInside = myView.point(inside: presser.location(in: myView), with: nil)
switch presser.state {
case .began, .changed:
if gestureIsInside {
myView.backgroundColor = .blue
} else {
myView.backgroundColor = .gray
}
case .cancelled:
myView.backgroundColor = .gray
case .ended:
myView.backgroundColor = .gray
if gestureIsInside {
doSomething()
}
default: break
}
}
You are not adding condition of gestureRecognizer state changed that's why it is accepting the end state.
#objc func longPressSelector(_ gestureRecognizer: UILongPressGestureRecognizer) {
if gestureRecognizer.state == .began {
} else if gestureRecognizer.state == .changed {
}else if gestureRecognizer.state == .ended {
}
}
Add one more condition and check if it works.
When you drag your finger outside of the view, gestureRecognizer is probably translated to failed or cancelled state, so you need to add handling of this cases gestureRecognizer.state == .failed and gestureRecognizer.state == .cancelled.
#objc func longPressSelector(_ gestureRecognizer: UILongPressGestureRecognizer) {
}
Place UILongPressGestureRecognizer instead of UITapGestureRecognizer and check it

How do I set it such that the pan recogniser increases a value

How do I set it such that the pan recogniser increases an Int variable when swiping right and then decreases the existing variable when sliding left.
#IBAction func customiseWhiteBalance(_ sender: UIPanGestureRecognizer) {
// Insert gesture value change here....
if sender.state == .began || sender.state == .changed {
let translation = sender.translation(in: self.view)
sender.setTranslation(CGPoint.zero, in: view)
print(translation)
}
}
Thanks
Pretty easy:
1: Add a counter:
var counter = 0
2: Declare your gestures and add them to your view (or where you need them)
let right = UISwipeGestureRecognizer(target: self, action: #selector(didViewSwipe(_:)))
right.direction = .right
self.view.addGestureRecognizer(right)
let left = UISwipeGestureRecognizer(target: self, action: #selector(didViewSwipe(_:)))
left.direction = .left
self.view.addGestureRecognizer(left)
3: Get the gesture and start the counting
func didViewSwipe(_ gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.right:
counter = counter + 1
case UISwipeGestureRecognizerDirection.left:
if counter > 0{
counter = counter - 1
}
default:
break
}
}
}

UIKit Dynamic - snap view to center of parent view

I try to build tinder like interface and I want View to snap with bounces to center of parent view, when finger is released. I try to implement it with snap behavios and pan gesture recognizer, but insted I see animation of falling view down.
My code is following
class ViewController: UIViewController {
var d = UIView()
var snap: UISnapBehavior!
var animator:UIDynamicAnimator!
override func viewDidLoad() {
super.viewDidLoad()
d.translatesAutoresizingMaskIntoConstraints = false
d.backgroundColor = .redColor()
view.addSubview(d)
d.heightAnchor.constraintEqualToConstant(150).active = true
d.widthAnchor.constraintEqualToConstant(150).active = true
d.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
d.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor).active = true
d.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: "pan:"))
animator = UIDynamicAnimator(referenceView: d)
}
func pan(gesture:UIPanGestureRecognizer) {
switch gesture.state {
case .Changed:
d.frame.origin.x = gesture.translationInView(d).x
case .Ended:
snap = UISnapBehavior(item: d, snapToPoint: view.center)
animator.addBehavior(snap)
default:
break
}
}
}
You should setup the referenceView of the UIDynamicAnimator to view and not d.
animator = UIDynamicAnimator(referenceView: view)
Here is the code I normally use for my pan gesture. It also tilts the block while panning:
func pan(gesture:UIPanGestureRecognizer) {
let panLocationInView = gesture.locationInView(view)
let panLocationInD = gesture.locationInView(d)
switch gesture.state {
case .Began:
animator.removeAllBehaviors()
let offset = UIOffsetMake(panLocationInD.x - CGRectGetMidX(d.bounds), panLocationInD.y - CGRectGetMidY(d.bounds))
attachmentBehaviour = UIAttachmentBehavior(item: d, offsetFromCenter: offset, attachedToAnchor: panLocationInView)
animator.addBehavior(attachmentBehaviour!)
case .Changed:
attachmentBehaviour?.anchorPoint = panLocationInView
case .Ended:
animator.removeAllBehaviors()
animator.addBehavior(UISnapBehavior(item: d, snapToPoint: view.center))
default:
break
}
}

programmatically simulate a swipe gesture in Swift

I am implementing a gesture recognizer for swiping in Swift. I wan to be able to simulate the flinging of the card (programmatically swipe the view).
I assumed there would be a built in function for this but all I have found is one for tap gesture not swipe gesture.
This is how I am implementing the swipe gesturing:
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged:"))
cardView.addGestureRecognizer(gesture)
cardView.userInteractionEnabled = true
}
func wasDragged (gesture: UIPanGestureRecognizer) {
let translation = gesture.translationInView(self.view)
let cardView = gesture.view!
// Move the object depending on the drag position
cardView.center = CGPoint(x: self.view.bounds.width / 2 + translation.x,
y: self.view.bounds.height / 2 + translation.y)
You can't simulate a gesture recognizer in its full implications (I mean, you can't actually make iOS think it's a real user action).
You can, however, fool your own code making it act as if it were a real swipe. For that, you need to create a gesture recognizer first:
var gestureRecognizerSwipeRight = UISwipeGestureRecognizer(target: self, action: "activatedGestureRecognizer:")
gestureRecognizerSwipeRight.direction = UISwipeGestureRecognizerDirection.Right
yourView.addGestureRecognizer(gestureRecognizerSwipeRight)
And then pass it directly to your action:
// Some other place in your code
self.activatedGestureRecognizer(gesture: gestureRecognizerSwipeRight)
Your activatedGestureRecognizer(gesture:) method should be something like:
func activatedGestureRecognizer(gesture: UIGestureRecognizer) {
if let gestureRecognizer = gesture as? UIGestureRecognizer {
// Here you can compare using if gestureRecognizer == gestureRecognizerSwipeRight
// ...or you could compare the direction of the gesture recognizer.
// It all depends on your implementation really.
if gestureRecognizer == gestureRecognizerSwipeRight {
// Swipe right detected
}
}
}
In fairness, I don't see any real gain in doing it this way. It should be a lot better to simply do the action associated with the swipe instead of actually simulating the gesture recognizer.
If you need, for instance, to animate your card while swipping, why don't you simply disable the user interaction on your card view and animate it programmatically?
Here is code you can use to programatically swipe the cell. When performed, the cell will animate to the swiped position.
#IBAction
func swipeFirstCell() {
if let cell = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) {
tableView.perform(Selector("_endSwipeToDeleteRowDidDelete:"), with: nil) // cancel any existing swipes
tableView.perform(Selector("_swipeToDeleteCell:"), with: cell)
}
}
Note you need to also return true for the cell from canEditRowAtIndexPath.
This might get flagged by Apple's private API detection but you can at least use it for development and perhaps obfuscate the string if you really to.
For SWIFT 3.0
let swipeRightOrange:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(slideToRightWithGestureRecognizer))
swipeRightOrange.direction = .Right;
let swipeLeftOrange:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(slideToLeftWithGestureRecognizer))
swipeLeftOrange.direction = .Left;
#IBAction func slideToLeftWithGestureRecognizer(gestureRecognizer:UISwipeGestureRecognizer)
{
viewOrange.backgroundColor = UIColor.blue
}
#IBAction func slideToRightWithGestureRecognizer(gestureRecognizer:UISwipeGestureRecognizer)
{
viewOrange.backgroundColor = UIColor.lightGray
}
You can create the UIPanGestureRecognizer by yourself and pass it to the wasDragged method. You should check with different values of the translation though:
let gesture = UIPanGestureRecognizer()
gesture.setTranslation(CGPointMake(0, 100), inView: self.view)
wasDragged(gesture)
SWIFT 4.2
let gesture = UIPanGestureRecognizer()
gesture.setTranslation(CGPoint(x: 0, y: 100), in: self.view)
wasDragged(gesture)
Although I asume you need something else. Why do you need to simulate this gesture in the first place?
You need to implement UISwipeGestureRecognizer
override func viewDidLoad() {
super.viewDidLoad()
var swipeRight = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeRight.direction = UISwipeGestureRecognizerDirection.Right
self.view.addGestureRecognizer(swipeRight)
var swipeDown = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeDown.direction = UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(swipeDown)
}
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.Right:
print("Swiped right")
case UISwipeGestureRecognizerDirection.Down:
print("Swiped down")
case UISwipeGestureRecognizerDirection.Left:
print("Swiped left")
case UISwipeGestureRecognizerDirection.Up:
print("Swiped up")
default:
break
}
}
}

How can I implement "drag right to dismiss" a View Controller that's in a navigation stack?

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.)
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 assign UINavigationController delegate. delegate will be needed for InteractiveTransitioning
.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!
Edit: Updated the code with more detailed solution.
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
}
}
}
}
In your collection view for example:
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
}
}
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
}
}
}
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
}
}
You need to investigate the interactivePopGestureRecognizer property of your UINavigationController.
Here is a similar question with example code to hook this up.
UINavigationController interactivePopGestureRecognizer working abnormal in iOS7
I think this is easier than the suggested solution and also works for all viewControllers inside that navigation and also for nested scrollviews.
https://stackoverflow.com/a/58779146/8517882
Just install the pod and then use EZNavigationController instead of UINavigationController to have this behavior on all view controllers inside that navigation controller.
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
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)
})
}
}
}
}

Resources