Adding a Customizable Swipe Down to Dismiss Functionality to any ViewController

Utilizing PanGestureRecognizer to create a reuable pull to dismiss functionlity.

Dawid Skiba
6 min readFeb 25, 2023
the swipe down to dismiss functionality

As many of you probably know, the swipe down to dismiss kinda already exists and is provided by UIKit. However, there’s a catch to it — the view controller in question has to have its modalPresentationStyle be one of the following; automatic, formSheet, pageSheet, or popover. For any other UIModalPresentationStyle, we are forced to write some fun custom code. In the upcoming part, I’ll go over how we can create a customizable swipe down to dismiss functionality using a simple UIPanGestureRecognizer. Additionally, we’ll add this functionality as an extension to UIViewController, allowing us to add it with just about two lines of code.

The Flexibility

I dropped the term customizable in the title, so let’s try to make it that. We’ll create a struct called SwipeDownConfiguration, which we’ll use to keep a reference to all the customization that will be added. We’ll also create various conditions that need to be met for the view to be dismissed, otherwise enabling it to bounce back into place if those conditions haven’t been met. Here are the list of things we will add:

  • An option to add resistance to the pan gesture, so that the view would drag half the amount is should. We’ll call this variable isSticky: Bool
  • An option for the view to fade out relative to its y-coordinate offset. We’ll name it shouldViewFade: Bool
  • An option to specify how fast (in seconds) the view would dismiss or retract back if the condition for dismiss is met/not met. We’ll call this animationDuration: CGFloat
  • One of the conditions we’ll use to determine if to dismiss the view is the velocity of the pan gesture. We’ll call it minimumVelocityToDismiss: CGFloat
  • Another condition we’ll use is the minimum vertical offset from the top of the screen. We will use percentage values from 0.0–0.9 (decimals to keep it simple). An example can be seen below. We’ll call this minimumScreenPercentageOffsetToDismiss: CGFloat
example with isSticky: true — minimumScreenPercentageOffsetToDismiss: 0.3 (30%)
  • The last thing we will add is a closure so we can notified when status of the pan gesture is changed. We will create an enum for all the possible statues, calledSwipeDownStatus. And we will call this variable statusChange: (SwipeDownStatus) -> Void.

Here are all the possible values for SwipeDownStatus:

  • initiated: pan gesture ended, conditions have been met, the view will begin to dismiss
  • completed: view is dismissed and completely out of view
  • cancelled: pan gesture ended but conditions were not met, the view will revert back to original position
example of when each SwipeDownStatus is notified through statusChange closure
the full SwipeDownConfiguration struct

The Pan Gesture

Next comes implementing the function that will be used by our UIPanGestureRecognizer, which we will create later. Basically, we have to move the view along with the pan gesture, alter the view based on any customization from theSwipeDownConfiguration, and detect if the conditions to dismiss have been met. For the code below, imagine we created a getter for SwipeDownConfiguration, but that part will be discussed later. Below is the onPan() function, any comment that includes a number will be discussed in more detail.

the function used by UIPanGestureRecognizer, with the explaination below
  1. If we can’t extract a configuration from the imagine getter, something is wrong, and we abort.
  2. We create a closure that will be responsible for transforming the view vertically, as well as adding any fading effect, if applicable. The parameter to for the closure will be the vertical offset from UIPanGestureRecognizer. We will use a simple CGAffineTransform to move the view.
  3. Another closure, this one is dedicated to notify statusChange() with .cancelled status, and it animate back the view to its original position.
  4. We start examining the panGesture.state. For the .began and .changed state, we only care about transforming the view vertically, so we use the moveViewVerticallyTo closure from before. Note, we use the adjustedTranslationY in case configuration.isSticky is true.
  5. If the gesture is in the .ended state, we determine if the conditions for dismiss have been met. We calculate the velocity and reference the configuration variables to determine is the conditions have been met.
  6. If the conditions have been met, we notify statusChange() with .initiated status, and trigger an animation for the view to go completely out of view. When that animation is complete, we notify statusChange() with .completed status.
  7. If the conditions have not been met, we reset the view using cancelAndReset().
  8. In the .undetermined state of the gesture, we just revert back to original position, using cancelAndReset() .

The Key For Recyclability

It would be great to make this pan gesture code reusable without having to copy pasta it to every file we need it in. One approach we can take is to create a protocol that requires variables for a pan gesture and the configuration. Then have default methods that connect everything together. But we can go a step further to keep things simple, without using a protocol. We will create an extension on UIViewController and use the magical associated objects. If you want to read more on them, here is a great explanation by Mattt. Basically we can create associated objects to store the pan gesture and the configuration inside an extension. We will create computed properties for both, with custom getter and setter to set up the respective associated object methods. This is the imaginary getter we used in the onPan() to refrence SwipeDownConfiguration.

The Extension

Now to combine everything, we can go ahead and create our extension. We need two methods, one to add the swipe down to dismiss with configuration struct as a parameter, and another one to remove any existing swipe down to dismiss functionality. The latter will come in handy in the former, to make sure anything lingering around is removed properly.

  • For the add method, we need to create a UIPanGestureRecognizer, add it to the view, and assign it along with the configuration using the associated objects created above.
  • For the remove method, we need to make sure we remove the pan gesture from the view and clear both the associated objects.

The Concoction

With everything created, all we have to do is create a file and combine all the code above. We would need to do a little upkeep and make all the associated objects and onPan() code private, and just leave the add and remove methods public for the extension. Here is a gist of the complete code. Now we are able to add this functionality to almost any UIViewController with only two lines of code (well more if you want to make it look pretty):

The Uncertainty

There are certain scenarios where this approach will not work nor is best suited for. Here are a couple of those scenarios:

  • Calling addSwipeDownToDismiss(...) on a subclass of UIViewController that has its view be anything besides a plain UIView. For example, a UITableViewController with its view being a UITableView.
  • If you don’t want to add swipe to directly to the view of the UIViewController. Though the code above can be tweaked make it more customizable.
  • If the UIViewController.view contains any scrollable subview. Also if the view has any other pan gesture recognizers that might interfere with the our pan gesture. In the below example, you can see a UIScrollView nested inside UIViewController.view, which is nested in a UINavigationController. With the addSwipeDownToDismiss(...) added to the UINavigationController, you can see only only the navigation bar reacting to the pan gesture.
an example of a undesirable experience

The End

I think that covers it all. The gifs used throughout this article are recorded from a demo app, which displays a simple UI to customize values for SwipeDownConfiguration. Allowing one to test the swipe down to dismiss with all various different configurations. The demo app, along with all the code can be found in this repository. Danke for reading!

#6seasonsandamovie

--

--

Dawid Skiba
0 Followers

focused on iOS development since 2015