iOS
UINavigationController
UIViewController
Swift

iOS Avoid duplicate view controllers with custom NavigationController

It is possible that duplicate view controllers may be accidently instantiated by the user. This is particularly a risk if the segue to a new view controller follows the response and processing of a REST request.

If the view controllers are managed by a UINavigationController, we can modify its behaviour to avoid duplicate view controllers. This method assumes that only one instance of each View Controller class is allowed at one time.

First, we provide a identifier for each Object (and therefore View Controller) based on its class.

extension NSObject {
    var className: String {
        get {
            return NSStringFromClass(type(of: self))
        }
    }
}

Then we subclass UINavigationController and override pushViewController; this method will now permit a new view controller to be added to the stack if
1. a view controller of this type does not already exist, and
2. another view controller is not already being added

class CustomNavigationController: UINavigationController {

    var isNewViewControllerBeingAdded: Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }

    func contains(viewController: UIViewController) -> Bool {
        return self.viewControllers.map{ $0.className }.contains(viewController.className)
    }

    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        if(!self.isNewViewControllerBeingAdded && !self.contains(viewController: viewController)) {
            self.isNewViewControllerBeingAdded = true
            super.pushViewController(viewController, animated: animated)
        }
    }
}

We also extend this subclass with a delegate handler, which modifies the "isNewViewControllerBeingAdded" flag upon successful addition of a view controller.

extension CustomNavigationController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
        self.isNewViewControllerBeingAdded = false
    }
}