single function to dismiss all open view controllers
Asked Answered
H

12

80

I have an app which is a single view application. I have a navigation controller linked up to all child controllers from the root view controller.

In each child controller, I have a logout button. I'm wondering if I can have a single function I can call which will dismiss all the controllers which have been open along along the way, no matter which controller was currently open when the user presses logout?

My basic start:

func tryLogout(){
     self.dismissViewControllerAnimated(true, completion: nil)
     let navigationController = UINavigationController(rootViewController: UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginViewController") )
     self.presentViewController(navigationController, animated: true, completion: nil)
}

I am looking for the most memory efficient way of carrying out this task. I will put my logout function in a separate utils file, but then I can't use self. And I still have the issue of knowing which controllers to dismiss dynamically.

Update Pop to root view controller has been suggested. So my attempt is something like:

func tryLogout(ViewController : UIViewController){
     print("do something")
     dispatch_async(dispatch_get_main_queue(), {
         ViewController.navigationController?.popToRootViewControllerAnimated(true)
         return
     })
 }

Would this be the best way to achieve what I'm after?

Haerr answered 4/11, 2015 at 11:38 Comment(5)
You might just want to reinitialize your app's UIWindow's rootViewController property. That will remove your entire view hierarchy (assuming you're not using other windows, which is rare).Diseur
Are you presenting controllers or just pushing onto the nav stack? Where is the logout button? Can you just pop to root? What other clean up do you need to do?Mandi
@Mandi I have my controller linked up with segue identifiers in certain cases and I am presentingViewControllers with the defined identifier. Then there are some cases where I'm just doing basic segues i.e. a button's action, a table row's click event. The logout button is added to the nav bar as a left bar button item. No other clean up really. Reset a few Global Prefs justHaerr
@Aaron Brager sorry if this is a silly question, but what do you mean exactly about using other windows?Haerr
@Haerr Very rarely an iOS app will have multiple UIWindows. You would know if you were making and managing them.Diseur
D
224

You can call :

self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)

Should dismiss all view controllers above the root view controller.

Diplomate answered 4/11, 2015 at 12:8 Comment(5)
Give this a go, let me know how it goes :), also assuming that self is the view controller that you are calling logout from. If it is called from somewhere else, you would need to pass the controller and replace self with the controller you would be calling logout from.Diplomate
@Zorayr just change false to true for dismissAnimation, it should work: self.view.window!.rootViewController?.dismissViewControllerAnimated(true, completion: nil)Jerryjerrybuild
@Diplomate self.view.window!.rootViewController?.dismissViewControllerAnimated(false, completion: nil) let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil) let initialViewControlleripad : UIViewController = mainStoryboardIpad.instantiateViewControllerWithIdentifier("LogIn") as UIViewController self.appDelegate.window = UIWindow(frame: UIScreen.mainScreen().bounds) self.appDelegate.window?.rootViewController = initialViewControlleripad self.appDelegate.window?.makeKeyAndVisible() This is my codeVerdugo
Suppose tested my app 5 times in that 3 times 'deinit {}' method is called sometime not. What is wrong with this ? And background my 'NSTimer' logic is workingVerdugo
Swift 3.0 syntax is self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)Kalikalian
D
52

Updated answer for Swift 4 and swift 5

UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true, completion: nil)

and when you use navigationController

self.navigationController?.popToRootViewController(animated: true)
Demurrage answered 22/7, 2017 at 7:11 Comment(0)
B
39

Works for Swift 4 and Swift 5

To dismiss any unwanted residue Modal ViewControllers, I used this and worked well without retaining any navigation stack references.

UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: false, completion: nil)

self.view.window! did crash for my case possibly because its a Modal screen and had lost the reference to the window.

Brackish answered 14/5, 2018 at 15:12 Comment(1)
Where to put it in completion block?M
W
9

Swift3

navigationController?.popToRootViewControllerAnimated(true)
Wallenstein answered 20/4, 2017 at 20:49 Comment(3)
this will close all pushed view controllers at one, good answerMas
This is a good answer, and doesn't need to be explained further.Resign
This doesn't work for the presented controllers, and only work for the controller that are being pushed in the stack. The solution is to find the presented view controller and dismiss itJinks
M
7

To dismiss all modal Views.

Swift 5


view.window?.rootViewController?.dismiss(animated: true, completion: nil)

Musketry answered 10/9, 2020 at 3:53 Comment(1)
Bit shameless to copy paste the accepted answer as a new answerGrained
C
6

Take a look at how unwind segues work. Its super simple, and lets you dismiss/pop to a certain viewcontroller in the heirarchy, even if it consists of a complex navigation (nested pushed and or presented view controllers), without much code.

Here's a very good answer (by smilebot) that shows how to use unwind segues to solve your problem https://mcmap.net/q/98729/-how-to-perform-unwind-segue-programmatically

Camarata answered 10/8, 2016 at 16:6 Comment(0)
G
1

If you have a customed UITabbarController, then try dismiss top viewController in UITabbarController by:

class MainTabBarController: UITabBarController {

    func aFuncLikeLogout() {

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

        //......
    }

}
Goodard answered 26/4, 2020 at 3:7 Comment(0)
A
1

If you have access to Navigation Controller, you can try something like this. Other solutions didn't work for me.

func popAndDismissAllControllers(animated: Bool) {
    var presentedController = navigationController?.presentedViewController

    while presentedController != nil {
        presentedController?.dismiss(animated: animated)
        presentedController = presentedController?.presentedViewController
    }

    navigationController?.popToRootViewController(animated: animated)
}
Ambros answered 5/8, 2022 at 15:42 Comment(0)
S
0

works for Swift 3.0 +

self.view.window!.rootViewController?.dismiss(animated: true, completion: nil)
Safar answered 2/12, 2017 at 8:0 Comment(0)
O
0

I have figured out a generic function to dismiss all presented controllers using the completion block.

extension UIWindow {
    static func keyWindow() -> UIWindow? {
        UIApplication.shared.windows.filter({ $0.isKeyWindow }).first
    }
}

func getVisibleViewController(_ rootViewController: UIViewController?) -> UIViewController? {
    
    var rootVC = rootViewController
    if rootVC == nil {
        rootVC = UIWindow.keyWindow()?.rootViewController
    }
    
    var presented = rootVC?.presentedViewController
    if rootVC?.presentedViewController == nil {
        if let isTab = rootVC?.isKind(of: UITabBarController.self), let isNav = rootVC?.isKind(of: UINavigationController.self) {
            if !isTab && !isNav {
                return rootVC
            }
            presented = rootVC
        } else {
            return rootVC
        }
    }
    
    if let presented = presented {
        if presented.isKind(of: UINavigationController.self) {
            if let navigationController = presented as? UINavigationController {
                return navigationController.viewControllers.last!
            }
        }
        
        if presented.isKind(of: UITabBarController.self) {
            if let tabBarController = presented as? UITabBarController {
                if let navigationController = tabBarController.selectedViewController! as? UINavigationController {
                    return navigationController.viewControllers.last!
                } else {
                    return tabBarController.selectedViewController!
                }
            }
        }
        
        return getVisibleViewController(presented)
    }
    return nil
}

func dismissedAllAlert(completion: (() -> Void)? = nil) {

    if let alert = UIViewController.getVisibleViewController(nil) {

    // If you want to dismiss a specific kind of presented controller then
    // comment upper line and uncomment below one

    // if let alert = UIViewController.getVisibleViewController(nil) as? UIAlertController {

        alert.dismiss(animated: true) {
            self.dismissedAllAlert(completion: completion)
        }

    } else {
        completion?()
    }

}

Note: You call anywhere in code at any class

Use:-

dismissedAllAlert()    // For dismiss all presented controller

dismissedAllAlert {    // For dismiss all presented controller with completion block
    // your code
}
Oidea answered 4/3, 2022 at 11:5 Comment(0)
L
0

I built this extension on UIViewController which dismisses presented views recursively and provides a completion block which can be used to present supplementary views after all of the dismissals are done.

public extension UIViewController {

    func popAndDismissAllControllers(animated: Bool, completion: (() -> Void)? = nil) {
        if let presentedController = navigationController?.presentedViewController {
            presentedController.dismiss(animated: animated) {
                self.dismissPresentedControllersRecursively(controller: presentedController, animated: animated, completion: {
                    self.navigationController?.popToRootViewController(animated: animated)
                    self.dismiss(animated: animated, completion: completion)
                })
            }
        } else {
            self.navigationController?.popToRootViewController(animated: animated)
            self.dismiss(animated: animated, completion: completion)
        }
    }
    
    private func dismissPresentedControllersRecursively(controller: UIViewController, animated: Bool, completion: @escaping () -> Void) {
        if let presentedController = controller.presentedViewController {
            presentedController.dismiss(animated: animated) {
                self.dismissPresentedControllersRecursively(controller: presentedController, animated: animated, completion: completion)
            }
        } else {
            completion()
        }
    }
}
Lickerish answered 12/4, 2024 at 8:1 Comment(0)
V
-1

Swift Used this to jump directly on your ROOT Navigation controller.

self.navigationController?.popToRootViewController(animated: true)

Versieversification answered 3/4, 2019 at 5:25 Comment(1)
popToRoot... doesnt dismiss presented controllers as they aren't added to navigation stack.Geoff

© 2022 - 2025 — McMap. All rights reserved.