How to write a pan gesture that intuitively dismisses a view by panning down

Written by
Rob Bentley

Originally Published on November 22, 2022
Republished on June 7, 2023

I recently wrote a view in Swift that behaved like a card view that animated up from the bottom of the screen when I wanted to show it. I even put really cute rounded corners on the top to make it look nice :)

Here is a slimmed down version of what you need to know. This is simply adding a subclassed UIView to your UIViewController:

@objc func showCardView() 
    self.cardView = CardView(frame: CGRect(x: 0, y: self.view.frame.size.height, width: self.view.frame.size.width, height: self.view.frame.size.height- (self.navigationController.navigationBar.frame.origin.y + self.navigationController.navigationBar.frame.size.height + 40)))
    self.view.addSubview(self.cardView)
    UIView.animate(withDuration: 0.125, delay: 0.0, options: .curveEaseInOut) {
        self.cardView.frame = CGRect(x: 0, y: self.navigationController.navigationBar.frame.origin.y + self.navigationController.navigationBar.frame.size.height + 40, width: self.cardView.frame.size.width, height: self.cardView.frame.size.height)
    }
}

Nothing too difficult there. Create the view at the bottom of the screen and then animate the frame up the screen after the view has been added.

Here is a look at the relevant parts of CardView that deal with the pan gesture:

class CardView: UIView 
    var panGestureRecognizer: UIPanGestureRecognizer?
    var originalPosition: CGPoint?
    var currentPositionTouched: CGPoint?

    init(frame: CGRect, journeys: [Journey]) {
        panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureAction(_:)))
        self.addGestureRecognizer(panGestureRecognizer!)
    }
    func yPoint(translationY: CGFloat) -> CGFloat {
        if translationY + 120 < 120 {
            return 120
        }
        return translationY + 120
    }

    @objc func panGestureAction(_ panGesture: UIPanGestureRecognizer) {
        let translation = panGesture.translation(in: self)
        if panGesture.state == .began {
            originalPosition = self.center
            currentPositionTouched = panGesture.location(in: self)
        } else if panGesture.state == .changed {
            self.frame.origin = CGPoint(
                x: 0,
                y: yPoint(translationY: translation.y)
            )
        } else if panGesture.state == .ended {
            let velocity = panGesture.velocity(in: self)
            if velocity.y >= 1500 {
                UIView.animate(withDuration: 0.125
                    , animations: {
                        self.self.frame.origin = CGPoint(
                            x: self.frame.origin.x,
                            y: self.frame.size.height
                        )
                    }, completion: { (isCompleted) in
                        if isCompleted {
                            self.closeXButtonPressed()
                        }
                    }
                )
            } else if translation.y > self.frame.size.height * 0.5 {
                UIView.animate(withDuration: 0.125
                    , animations: {
                        self.self.frame.origin = CGPoint(
                            x: self.frame.origin.x,
                            y: self.frame.size.height
                        )
                    }, completion: { (isCompleted) in
                        if isCompleted {
                            self.closeXButtonPressed()
                        }
                    }
                )
            } else {
                UIView.animate(withDuration: 0.2, animations: {
                        self.center = self.originalPosition!
                    }
                )
            }
        }
    }
}

On CardView, we initialize it and add a UIPanGestureRecognizer, and we create variables for the original position touched on the screen, as well as the current position of your touch.

In the delegate method for panGestureAction, we are doing a couple of things.

First, if we move our finger down really fast, we animate the view off the screen. Iโ€™m calling โ€œself.closeXButtonPressed()โ€ here, which performs that action on the ViewController.

Second, if we move the view halfway down the screen or more, we animate it off the screen. If we start moving the view, but we stop, and the view origin is above the half the height of the screen, we animate the view back to its original position.


Robert Bentley is a self-taught developer who's been building mobile software for over ten years. As the CEO of JMG each project, platform update, and emerging tech integration brings something new to learn, and he shares these short tips in hopes they help someone else doing something for the first time. To learn more about Rob, connect with him on LinkedIn.

Join our newsletter community

Oh no, there was an error with your email!

Hey, thank you so much for signing up! We've got your address saved, so look forward to an email from us soon. ๐ŸŽ‰

We respect your privacy.