Search code examples
iosswiftuiviewcontrolleruikit

How to dismiss a specific UIViewController on the backstack without dismissing other UIViewControllers? (UIKit)


I have several UIViewControllers that I present programmatically with modalPresentationStyle = .fullScreen. My stack of UIViewControllers looks like this:

ViewControllerA -> ViewControllerB -> ViewControllerC -> ViewControllerD

What I want to do is dismiss ViewControllerB from ViewControllerD (by clicking a button) so the stack now looks like this:

ViewControllerA -> ViewControllerC -> ViewControllerD

What I tried:

I tried registering a NotificationCenter listener in ViewControllerB and then posting a notification from ViewControllerD to dismiss ViewControllerB, but that ended up dismissing ViewControllerD.

I tried nesting all the UIViewControllers in a UINavigationController and popping ViewControllerB in ViewControllerD like this:

if let viewController = navigationController?.viewControllers.first(where: {$0 is ViewControllerB}) {
     navigationController?.popToViewController(viewController, animated: false)
}

But that also ended up just popping ViewControllerD.

I tried this:

self.presentingViewController?.presentingViewController?.dismiss(animated: false, completion: nil)

But the problem is you end up dismissing ViewControllerB and ViewControllerC too. I want to just dismiss ViewControllerB and leave the other UIViewControllers as they are.

For clarification, I tried both using UINavigationController and just pushing modals one on top of one another. I don't mind either pattern, as long as I can on-demand destroy UIViewControllers that aren't currently visible, without affecting the chain of the other UIViewControllers in the order they were presented/pushed.


Solution

  • ViewControllerA -> ViewControllerB -> ViewControllerC -> ViewControllerD

    It's a stack if you use UINavigationController and push action. Then you had an array of view controllers that are held by NavigationController.

    However, in the scenario above, you're using present rather than push, because I saw the modalPresentationStyle property here. presentViewController:animated:completion: works differently. You can imagine that push is a kind of hierarchical horizontal, and present, on the other hand, is a hierarchical vertical. UIKit allows you to present a single view controller on the current view controller at a time:

    vcD above vcC above vcB above vcA

    You can simply see the dependencies here since each controller that has a higher layer depends on the lower one. So, whenever you want to break a node, all nodes from this node to the end will be broken too. i.e, dismiss vcB and also dismiss vcC -> vcD. etc


    Update If that's the case as you mentioned, you can try this:

    if let indexToRemove = navigationController?.viewControllers.firstIndex(where: { $0 is viewControllerB }) {
        navigationController?.viewControllers.remove(at: indexToRemove)
        let remainControllers = navigationController?.viewControllers ?? []
        navigationController?.setViewControllers(remainControllers, animated: false)
    }