Dismiss ViewControllers Presented Modally Using Swipe Down

qbo
2 min readMar 29, 2017

--

Long title, I know. If you don’t understand what behaviour I’m describing, I hope the gif below from my app, Little Windows, helps.

First, let’s setup two ViewControllers — VC1 and VC2. VC1 will be modally pushing VC2 via button press.

Now, 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.

Next, add ‘Pan Gesture Recognizer’ to VC2 and connect it to the UIView. Don’t forget to send its action to a selector in VC2.

In the source code of VC2, add CGPoint variable that can hold the x, y value of initial touch point (i.e. initialTouchPoint). The initialTouchPoint will be used to compare subsequent touch points while dragging so we can move VC2 according to the difference.

Inside the pan gesture recognizer selector (i.e. panGestureRecognizerHandler), create a variable to store the touch point of the instance (i.e. touchPoint).

let touchPoint = sender.location(in: self.view?.window)

Then, observe the type of the touch — whether it began, changed, or ended/cancelled using UIGestureRecognizerState. We want to store the touch point in initialTouchPoint if touch has ‘began’, compare initialTouchPoint with touchPoint if ‘changed’, and determine if we want to dismiss or restore VC2 if ‘ended/cancelled’.

If ‘began’:

initialTouchPoint = touchPoint

If ‘changed’, we get the vertical difference (y) and move the CV2 vertically appropriately. We check whether the vertical difference is great than 0 because we want to allow CV2 to only be moveable downwards:

if (touchPoint.y - initialTouchPoint.y > 0) {
self.view.frame = CGRect(x: 0, y: touchPoint.y - initialTouchPoint.y, width: self.view.frame.size.width, height: self.view.frame.size.height)
}

If ‘ended/cancelled’, we see if the downward gesture resulted in a movement more than 100 pixels. The 100-pixel value is arbitrary, the bigger it is, the more effort user needs to put in to dismiss CV2. I find 100 is a reasonable value to provide a good UX. If vertical difference is more than 100, we simply dismiss CV2, otherwise we bring it back to its original position using simple animation:

if touchPoint.y - initialTouchPoint.y > 100 {
self.dismiss(animated: true, completion: nil)
} 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)
})
}

That’s basically all there is to dismiss any ViewController presented modally using swipe down gesture. Here’s everything that goes inside the panGestureRecognizerHandler:

--

--