4

Within my app I'm having an issue with the following error:

Pushing the same view controller instance more than once is not supported

It's a bug report that's comeback from a few users. We've tried to replicate it but can't (double tapping buttons etc). This is the line we use to open the view controller:

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let editView = storyboard.instantiateViewControllerWithIdentifier("EditViewController") as! EditViewController
editView.passedImage = image
editView.navigationController?.setNavigationBarHidden(false, animated: false)
if !(self.navigationController!.topViewController! is EditViewController) {
   self.navigationController?.pushViewController(editView, animated: true)
}

Anybody have any ideas? I've done a bit of research and most answers on Stack we've covered so are at a bit of a loss for how to investigate.

Jahoe
  • 1,666
  • 2
  • 12
  • 27

4 Answers4

9

Try this to avoid pushing the same VC twice:

if !(self.navigationController!.viewControllers.contains(editView)){
    self.navigationController?.pushViewController(editView, animated:true)
}
Elena
  • 829
  • 8
  • 20
3

As the pushViewController is asynchronous since iOS7, if you tap the button that push view controller too fast, it will be pushed twice. I have met such issue, the only way I tried is to set a flag when push is invoked (i.e - navigationController:willShowViewController:animated:) and unset the flag when the delegate of UINavigationController is called - navigationController:didShowViewController:animated:

It's ugly, but it can avoid the twice-push issue.

user3349433
  • 480
  • 2
  • 11
3

In the function that does the push:

guard navigationController?.topViewController == self else { return }

Sam
  • 3,659
  • 3
  • 36
  • 49
  • It's a really good solution, but doesn't work 100% times. For example, when segue is in progress. – Nadzeya Mar 14 '19 at 22:23
2

The completion block of CATransaction to the rescue :)

The animation of pushViewController(:animated:) is actually pushed onto the CATransaction stack, which is created by each iteration of the run loop. So the completion block of the CATransaction will be called once the push animation is finished.

We use a boolean variable isPushing to make sure new view controller can't be pushed while already pushing one.

class MyNavigationController: UINavigationController {
    var isPushing = false

    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        if !isPushing {
            isPushing = true
            CATransaction.begin()
            CATransaction.setCompletionBlock {
                self.isPushing = false
            }
            super.pushViewController(viewController, animated: animated)
            CATransaction.commit()
        }
    }
}
Lizhen Hu
  • 814
  • 10
  • 16
  • Nice, but it's not always blocking. It works in all places in my app except one: I have PageViewController and from controller inside it I'm calling pushViewController. It's calling the overridden function, but still sometimes is called second time after first push is done. I had to add another blockade in this particular class. – Makalele Nov 08 '19 at 11:46