interactivePopGestureRecognizer corrupts navigation stack on root view controller
Asked Answered
P

7

27

In my UINavigationController I added custom back buttons with the side effect that it is not possible anymore to swipe left to right to pop the view controller and navigate back.

So I implemented interactivePopGestureRecognizer in my custom UINavigationController class:

class UINavigationControllerExtended: UINavigationController, UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        if self.respondsToSelector(Selector("interactivePopGestureRecognizer")) {
            self.interactivePopGestureRecognizer?.delegate = self
        }
    }

    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return gestureRecognizer.isKindOfClass(UIScreenEdgePanGestureRecognizer)
    }
}

This works fine except when I am in my root view controller (RVC) which is a UICollectionViewController, i.e. the most bottom view controller in the navigation stack. When I do the swipe left to right gesture, nothing seems to happen, as expected. But when I then tap a UICollectionViewCell the destination view controller (DVC) does not get pushed over the RVC. Instead I only see the DVC's shadow on the right side of the screen.

The RVC is not responsive anymore, but as I swipe left to right again, the DVC interactively moves right to left into the screen. When I finish the gesture, the DVC moves completely into the screen just to quickly disappear left to right again. The RVC becomes responsive again.

So it seems the DVC gets pushed onto the navigation stack but not visibly into the screen.

Any suggestions where this strange behaviour originates?

Pruter answered 9/1, 2016 at 19:36 Comment(0)
H
14

Implement UINavigationControllerDelegate for your navigation controller and enable/disable the gesture recognizer there.

// Fix bug when pop gesture is enabled for the root controller
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
    self.interactivePopGestureRecognizer?.enabled = self.viewControllers.count > 1
}

Keeping the code independent from the pushed view controllers.

Hopkins answered 12/9, 2016 at 19:48 Comment(1)
Thanks, this fixed the same problem here :)Clovis
P
9

My current solution is to disable the interactivePopGestureRecognizer in the root view controller:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    self.navigationController?.interactivePopGestureRecognizer?.enabled = false
}

In the first child view controller I enable it again. But this seems to be more a workaround because I don't understand the actual problem why the navigation stack got messed up in the first place.

Pruter answered 9/1, 2016 at 19:39 Comment(2)
Hi Manuel. I'm dealing with the same issue. Specificity of my implementation is that I'm using a custom popGesture so it extends not only to the edges but to the whole screen (a la Instagram). Surprisingly, despite the solutions given here, I'm getting the same strange bug now if I spam my root vc (a UICollectionViewController, like yours) with swipes then tap an item in the collectionView. Did you eventually find a proper fix for that? Thanks!Alcaeus
@Alcaeus I did not find a fix beyond the workaround in this answer.Pruter
G
7

Here is my solution for this. Swift 4.2

Implement UIGestureRecognizerDelegate and UINavigationControllerDelegate protocols. In my case I did this in an UITabBarController that would be also my root view controller.

Then, on viewDidLoad, do:

override func viewDidLoad() {
    super.viewDidLoad()

    self.navigationController?.interactivePopGestureRecognizer?.delegate = self
    self.navigationController?.delegate = self
}

Then, add the delegate method for UINavigationControllerDelegate and check if it is the root view by counting the number of view on the navigation controller and disable it if it is or enable it if its not.

func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    let enable = self.navigationController?.viewControllers.count ?? 0 > 1
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = enable
}

Lastly, add the UIGestureRecognizerDelegate method

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

This should fix the problem without the necessity of manually enabling/disabling the gesture recogniser in every view of your project.

Griefstricken answered 9/10, 2018 at 9:32 Comment(0)
M
1

Solution from Guilherme Carvalho worked for me, but I had to assign delegate methods in viewWillAppear, in viewDidLoad was too late for my implementation.

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        navigationController?.interactivePopGestureRecognizer?.delegate = self
        navigationController?.delegate = self
    }
Manor answered 23/2, 2022 at 10:38 Comment(0)
C
0
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    self.interactivePopGestureRecognizer?.isEnabled = self.viewControllers.count > 1
}

Updated @rivera solution for Swift 5

Clovis answered 9/12, 2020 at 11:14 Comment(0)
N
0

In your base navigation controller set the delegate to self

self.interactivePopGestureRecognizer?.delegate = self

Then add the following delegate function

  func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    if viewControllers.count <= 1 {
        return false
    } else {
        return true
    }
}
Newson answered 5/6 at 21:40 Comment(0)
C
-2

Try this code

func navigationController(navigationController: UINavigationController, didShowViewController vc: UIViewController, animated: Bool) {
    self.interactivePopGestureRecognizer?.enabled = self.vc.count > 1
}
Caceres answered 27/2, 2017 at 10:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.