I like the smell of Swift in the morning…

Enable the Swipe Back Gesture (aka Interactive Pop Gesture) when using a UINavigationController with custom back button

Posted: | Author: | Filed under: iOS, Swift | Tags: , | 11 Comments »

Who doesn’t love the “Swipe Back” gesture to navigate back to the previous view controller? I rarely use the back button in the left upper corner of the screen anymore. Especially when you are holding your phone in one hand it is much more easy to just swipe back.

The best thing is that as a developer you don’t have to do anything to enable this swipe back functionality as it already part of UINavigationController.

On initialization UINavigationController installs a UIScreenEdgePanGestureRecognizer on its view to handle the interactive pop gesture (aka “Swipe Back” gesture). You can access the gesture recognizer via the interactivePopGestureRecognizer property.

Now, when you decide to replace the default back button with your own custom back button you will see that the “Swipe Back” Gesture is not working anymore. Apparently the UIScreenEdgePanGestureRecognizer’s delegate only allows the gesture to be recognized when it sees that the default back button is being used. Bummer!

To make the “Swipe Back” work again you have to bypass the delegate that disables the gesture. If found some suggestions that would simply set the delegate to nil. This seems to work at first. But when you start playing around with your app after doing that you will eventually see that the app freezes and does not recognize ANY touches anymore. This happens when you swipe back while the navigation controller is pushing a view controller. Not good!

So you have to set the delegate yourself and implement gestureRecognizerShouldBegin(_:) to disable the gesture whenever the navigation controller is pushing a view controller.

The easiest way to do this is to subclass UINavigationController:

To block the “Swipe Back” gesture there is a boolean property isPushingViewController that is true when the navigation controller is pushing a view controller. [// 1]

Setting the property to true is easy. We simply override pushViewController(_:animated:) and set isPushingViewController to true [// 2]

To be able to set it back to false when the view controller has been pushed we have to know when the push is completed. Luckily there is a UINavigationControllerDelegate method that we can use. So we set our UINavigationController subclass’ delegate to self [// 3] and implement navigationController(_:didShowViewController:animated:) to set isPushingViewController back to false [// 4]

Now all that is left is to also set the interactivePopGestureRecognizer‘s delegate to self [// 5] and to only allow the gesture when isPushingViewController is false. And while we are at it we also make sure that the swipe back makes sense. In other words we check if the navigation controller has more than one view controller on his stack. Otherwise we ignore the gesture. [// 6]

Yay, “Swipe Back” is back!

But wait a minute. What happens when someone is using this UINavigationController subclass and needs to set the delegate themselves:

This won’t work, because we are overwriting the delegate in our subclass’ viewDidLoad method.

To make this work, we need to keep a reference to the delegate before overwriting it and forward all delegate method calls to it:

We added two things:

// 1: We use the didSet property observer on the delegate property. Whenever the delegate is being set we check if our subclass itself is the delegate (we don’t do anything so the subclass will be set as its own delegate). If the delegate is of any other class we do not set it as delegate but keep a reference to it.

// 2: We implement the UINavigationControllerDelegate method that we use to see when the pushing is completing. In this method we set isPushingViewController back to false. We also forward the call to our external delegate.

Unfortunately we have to forward ALL methods that are defined in UINavigationControllerDelegate to out external delegate to make the external delegate fully functional. This is not an ideal solution. When Apple decides to add methods to UINavigationControllerDelegate in the future they will have to added here. Or they won’t be called when setting the delegate manually. In Objective-C you could use the NSInvocation API to handle this, but that API is not available in Swift. I have not found a way to forward all delegate methods to the external delegate in Swift. I you know a way to do this, please leave a comment!

And so, with a little bit of work, we can use the Interactive Pop Gesture (aka “Swipe Back”) even when we are not using the system default back button.


  • 1

    Francesco Prospatosaid at

    This article was extremly helpful and informative, now I can swipe to go back. But now the back button on the top left of any view is disabled. How do I implement a function for when the user presses the back button it will go back to the previous view (just like swiping back)?

  • 2

    Jörnsaid at

    Hi Francesco,

    thanks for your comment! When you use a custom back button you have to implement its behavior yourself. In other words: you are responsible to pop the current view controller when the user presses the back button. You can add a UITapGestureRecognizer to you back button to achieve this.

  • 3

    Nathansaid at

    Great thank you.
    You have a potential infinite recursion in delegate didSet. delegate = oldValue will call didSet again and if oldValue != InteractivePopNavigationController, it will be called again, then over and over again.

  • 4

    Jörnsaid at

    Hi Nathan,

    thanks for your comment, I highly appreciate feedback like this! In this case there is no infinite recursion because if you assign a value to a property within its own didSet observer, the new value that you assign simply replaces the one that was just set. It does not call the setter again and thus does not create an infinite recursion loop.

  • 5

    Dennis Lausaid at

    This is ingenious! Thank you so much for sharing this piece of code!

  • 6

    Sagarsaid at

    Grate Thanks For this.

  • 7

    Alexsaid at


    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
    externalDelegate?.navigationController?(navigationController, didShow: viewController, animated: animated)

    shold be corrected to …willShow….

    Am i correct?

    P.S. Great answer! Cool

  • 8

    Jörnsaid at

    Hi Alex,

    you are totally right, thanks for pointing that out! I changed the code example to fix this.

  • 9

    Borissaid at

    Thanks a lot for this post, it is very helpful!

    I get an issue though, when presenting this controller, infinite recursion in navigationControllerPreferredInterfaceOrientationForPresentation.

    This is because the default implementation for self.preferredInterfaceOrientationForPresentation is using delegate’s navigationControllerPreferredInterfaceOrientationForPresentation(), which creates recursion.

    I my case I added:

    override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
    // Don’t use super.preferredInterfaceOrientationForPresentation, because it will just call
    // delegate.navigationControllerPreferredInterfaceOrientationForPresentation resulting in
    // infinite recursion.
    return .portrait

  • 10

    Muratsaid at


    I just lost animations of dragging the view controller at the top.

    Do you know how to get animations work with your code?

  • 11

    Jörnsaid at

    Hi Murat, thanks for your comment. I’d love to help you but I am afraid I don’t understand the problem yet. Can you please describe what animations are not working?

Leave a Reply