47

I have a long View Controllers hierarchy;

in the first View Controller I use this code:

SecondViewController *svc = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
[self presentModalViewController:svc animated:YES];    
[svc release];

In the second View Controller I use this code:

ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
[self presentModalViewController:tvc animated:YES];    
[tvc release];

and so on.

So there is a moment when I have many View Controllers and I need to come back to the first View Controller. If I come back one step at once, I use in every View Controller this code:

[self dismissModalViewControllerAnimated:YES];

If I want to go back directly from the, say, sixth View Controller to the first one, what I have to do to dismiss all the Controllers at once?

Thanks

Oscar Peli
  • 1,102
  • 1
  • 11
  • 18

22 Answers22

66

Yes. there are already a bunch of answers, but I'm just going to add one to the end of the list anyway. The problem is that we need to get a reference to the view controller at the base of the hierarchy. As in @Juan Munhoes Junior's answer, you can walk the hierarchy, but there may be different routes the user could take, so that's a pretty fragile answer. It is not hard to extend this simple solution, though to simply walk the hierarchy looking for the bottom of the stack. Calling dismiss on the bottom one will get all the others, too.

-(void)dismissModalStack {
    UIViewController *vc = self.presentingViewController;
    while (vc.presentingViewController) {
        vc = vc.presentingViewController;
    }
    [vc dismissViewControllerAnimated:YES completion:NULL];
}

This is simple and flexible: if you want to look for a particular kind of view controller in the stack, you could add logic based on [vc isKindOfClass:[DesiredViewControllerClass class]].

Suz
  • 3,624
  • 3
  • 21
  • 26
27

I found the solution.

Of course you can find the solution in the most obvious place so reading from the UIViewController reference for the dismissModalViewControllerAnimated method ...

If you present several modal view controllers in succession, and thus build a stack of modal view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.

so it's enough to call the dismissModalViewControllerAnimated on the target View. I used the following code:

[[[[[self parentViewController] parentViewController] parentViewController] parentViewController] dismissModalViewControllerAnimated:YES];

to go back to my home.

Oscar Peli
  • 1,102
  • 1
  • 11
  • 18
  • 11
    NOTE: In iOS5 this changed to "presentingViewController": http://game4mob.com/index.php/jawbreaker/66-dismiss-modal-view-in-ios5-sdk – Tom Kidd May 23 '12 at 22:22
  • 2
    Caveat: i you do not precisely now how much view you must pop, it doesn't work fine. – Sasha Grievus Jul 31 '14 at 13:44
  • yea just use this `[self.presentingViewController dismissViewControllerAnimated:NO completion:nil];` works anywhere – Cbas Jul 26 '16 at 17:55
18

iOS 8+ universal method for fullscreen dismissal without wrong animation context. In Objective-C and Swift

Objective-C

- (void)dismissModalStackAnimated:(bool)animated completion:(void (^)(void))completion {
    UIView *fullscreenSnapshot = [[UIApplication sharedApplication].delegate.window snapshotViewAfterScreenUpdates:false];
    [self.presentedViewController.view insertSubview:fullscreenSnapshot atIndex:NSIntegerMax];
    [self dismissViewControllerAnimated:animated completion:completion];
}

Swift

func dismissModalStack(animated: Bool, completion: (() -> Void)?) {
    if let fullscreenSnapshot = UIApplication.shared.delegate?.window??.snapshotView(afterScreenUpdates: false) {
        presentedViewController?.view.addSubview(fullscreenSnapshot)
    }
    if !isBeingDismissed {
        dismiss(animated: animated, completion: completion)
    }
}

tl;dr

What is wrong with other solutions?

There are many solutions but none of them count with wrong dismissing context so:

e.g. root A -> Presents B -> Presents C and you want to dismiss to the A from C, you can officialy by calling dismissViewControllerAnimated on rootViewController.

 [[UIApplication sharedApplication].delegate.window.rootViewController dismissModalStackAnimated:true completion:nil];

However call dismiss on this root from C will lead to right behavior with wrong transition (B to A would have been seen instead of C to A).


so

I created universal dismiss method. This method will take current fullscreen snapshot and place it over the receiver's presented view controller and then dismiss it all. (Example: Called default dismiss from C, but B is really seen as dismissing)

Community
  • 1
  • 1
Jakub Truhlář
  • 15,319
  • 7
  • 65
  • 73
15

Say your first view controller is also the Root / Initial View Controller (the one you nominated in your Storyboard as the Initial View Controller). You can set it up to listen to requests to dismiss all its presented view controllers:

in FirstViewController:

- (void)viewDidLoad {
    [super viewDidLoad];

    // listen to any requests to dismiss all stacked view controllers
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissAllViewControllers:) name:@"YourDismissAllViewControllersIdentifier" object:nil];

    // the remainder of viewDidLoad ...
}

// this method gets called whenever a notification is posted to dismiss all view controllers
- (void)dismissAllViewControllers:(NSNotification *)notification {
    // dismiss all view controllers in the navigation stack
    [self dismissViewControllerAnimated:YES completion:^{}];
}

And in any other view controller down the navigation stack that decides we should return to the top of the navigation stack:

[[NSNotificationCenter defaultCenter] postNotificationName:@"YourDismissAllViewControllersIdentifier" object:self];

This should dismiss all modally presented view controllers with an animation, leaving only the root view controller. This also works if your initial view controller is a UINavigationController and the first view controller is set as its root view controller.

Bonus tip: It's important that the notification name is identical. Probably a good idea to define this notification name somewhere in the app as a variable, as not to get miscommunication due to typing errors.

Thomas Verbeek
  • 2,207
  • 23
  • 29
6
[[self presentingViewController]presentingViewController]dismissModalViewControllerAnimated:NO];

You can also implement a delegate in all controllers you want to dismiss

  • dismissModalViewController is depricated – Sumit Kumar Saha Sep 22 '16 at 11:36
  • You can create a delegate and enable in all view you want to dismiss, so normal dismiss it one by one in view will apppear – Juan Munhoes Junior Sep 22 '16 at 11:38
  • Juan, my issue is i am not able to dismiss the viewcontrollers present on the navigation stack. I have gone through several post on SO, but no help. – Sumit Kumar Saha Sep 22 '16 at 12:01
  • The VCs tha i have are in the order preseing on buton1 goes thorugh 1->2->3->4->5 and for button2 it goes through 1->2->4->5. And i am not able to dismiss the VC no. 2 so as to land up at VC no.1 .... Is there any tight coupling bw VCs too as in parent-child hierarchy? – Sumit Kumar Saha Sep 22 '16 at 12:04
5

The problem with most solutions is that when you dismiss the stack of presented viewControllers, the user will briefly see the first presented viewController in the stack as it is being dismissed. Jakub's excellent solution solves that. Here is an extension based on his answer.

extension UIViewController {

    func dismissAll(animated: Bool, completion: (() -> Void)? = nil) {
        if let optionalWindow = UIApplication.shared.delegate?.window, let window = optionalWindow, let rootViewController = window.rootViewController, let presentedViewController = rootViewController.presentedViewController  {
            if let snapshotView = window.snapshotView(afterScreenUpdates: false) {
                presentedViewController.view.addSubview(snapshotView)
                presentedViewController.modalTransitionStyle = .coverVertical
            }
            if !isBeingDismissed {
                rootViewController.dismiss(animated: animated, completion: completion)
            }
        }
    }

}

Usage: Call this extension function from any presented viewController that you want to dismiss back to the root.

@IBAction func close() {
    dismissAll(animated: true)
}
Elijah
  • 7,223
  • 2
  • 49
  • 49
4

If you are using all are Model view controller you can use notification for dismissing all preseted view controller.

1.Register Notification in RootViewController like this

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(dismissModelViewController)
                                             name:dismissModelViewController
                                           object:nil];

2.Implement the dismissModelViewController function in rootviewController

- (void)dismissModelViewController
{
    While (![self.navigationController.visibleViewController isMemberOfClass:[RootviewController class]])
    {
        [self.navigationController.visibleViewController dismissViewControllerAnimated:NO completion:nil];
    }
}

3.Notification post every close or dismiss button event.

   [[NSNotificationCenter defaultCenter] postNotificationName:dismissModelViewController object:self];
Chathurka
  • 517
  • 4
  • 11
4

In Swift:

self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
Undo
  • 25,204
  • 37
  • 102
  • 124
richyrich24
  • 1,347
  • 2
  • 9
  • 9
3

Try this..

ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
[self.view addsubview:tvc];    
[tvc release];
Mehul Mistri
  • 14,844
  • 14
  • 66
  • 94
Chu Chau
  • 70
  • 1
  • 4
2

Swift 3 extension based upon the above answers.

Principle for a stack like that : A -> B -> C -> D

  • Take a snapshot of D
  • Add this snapshot on B
  • Dismiss from B without animation
  • On completion, dismiss from A with animation

    extension UIViewController {
    
        func dismissModalStack(animated: Bool, completion: (() -> Void)?) {
            let fullscreenSnapshot = UIApplication.shared.delegate?.window??.snapshotView(afterScreenUpdates: false)
            if !isBeingDismissed {
                var rootVc = presentingViewController
                while rootVc?.presentingViewController != nil {
                    rootVc = rootVc?.presentingViewController
                }
                let secondToLastVc = rootVc?.presentedViewController
                if fullscreenSnapshot != nil {
                    secondToLastVc?.view.addSubview(fullscreenSnapshot!)
                }
                secondToLastVc?.dismiss(animated: false, completion: {
                    rootVc?.dismiss(animated: true, completion: completion)
                })
            }
        }
    }
    

A little flickering on simulator but not on device.

squall2022
  • 214
  • 4
  • 9
1
  id vc = [self presentingViewController];
  id lastVC = self;
  while (vc != nil) {
    id tmp = vc;
    vc = [vc presentingViewController];
    lastVC = tmp;
  }
  [lastVC dismissViewControllerAnimated:YES completion:^{
}];
Ashish Awaghad
  • 2,822
  • 3
  • 22
  • 33
1

Use this generic solution to solve this issue:

- (UIViewController*)topViewController
{
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }
    return topController;
}


- (void)dismissAllModalController{

    __block UIViewController *topController = [self topViewController];

    while (topController.presentingViewController) {
        [topController dismissViewControllerAnimated:NO completion:^{

        }];
        topController = [self topViewController];
    }
}
halfer
  • 18,701
  • 13
  • 79
  • 158
SachinVsSachin
  • 6,193
  • 2
  • 31
  • 37
1

Here is a solution that I use to pop and dismiss all view controllers in order to go back to the root view controller. I have those two methods in a category of UIViewController:

+ (UIViewController*)topmostViewController
{
    UIViewController* vc = [[[UIApplication sharedApplication] keyWindow] rootViewController];
    while(vc.presentedViewController) {
        vc = vc.presentedViewController;
    }
    return vc;
}

+ (void)returnToRootViewController
{
    UIViewController* vc = [UIViewController topmostViewController];
    while (vc) {
        if([vc isKindOfClass:[UINavigationController class]]) {
            [(UINavigationController*)vc popToRootViewControllerAnimated:NO];
        }
        if(vc.presentingViewController) {
            [vc dismissViewControllerAnimated:NO completion:^{}];
        }
        vc = vc.presentingViewController;
    }
}

Then I just call

[UIViewController returnToRootViewController];
Nikolay Spassov
  • 1,288
  • 1
  • 12
  • 22
1

A swift version with some additions based on this comment

func dismissModalStack(viewController: UIViewController, animated: Bool, completionBlock: BasicBlock?) {
    if viewController.presentingViewController != nil {
        var vc = viewController.presentingViewController!
        while (vc.presentingViewController != nil) {
            vc = vc.presentingViewController!;
        }
        vc.dismissViewControllerAnimated(animated, completion: nil)

        if let c = completionBlock {
            c()
        }
    }
}
Community
  • 1
  • 1
Tejaswi Yerukalapudi
  • 8,629
  • 12
  • 57
  • 101
1

Simple recursive closer:

extension UIViewController {
    final public func dismissEntireStackAndSelf(animate: Bool = true) {
        // Always false on non-calling controller
        presentedViewController?.ip_dismissEntireStackAndSelf(false)
        self.dismissViewControllerAnimated(animate, completion: nil)
    }
}

This will force close every child controller and then only animate self. You can toggle for whatever you like, but if you animate each controller they go one by one and it's slow.

Call

baseController.dismissEntireStackAndSelf()
Logan
  • 49,229
  • 19
  • 92
  • 123
1

Swift extension based upon the above answers:

extension UIViewController {

    func dismissUntilAnimated<T: UIViewController>(animated: Bool, viewController: T.Type, completion: ((viewController: T) -> Void)?) {
        var vc = presentingViewController!
        while let new = vc.presentingViewController where !(new is T) {
            vc = new
        }
        vc.dismissViewControllerAnimated(animated, completion: {
            completion?(viewController: vc as! T)
        })
    }
}

Swift 3.0 version:

extension UIViewController {

    /// Dismiss all modally presented view controllers until a specified view controller is reached. If no view controller is found, this function will do nothing.

    /// - Parameter reached:      The type of the view controller to dismiss until.
    /// - Parameter flag:         Pass `true` to animate the transition.
    /// - Parameter completion:   The block to execute after the view controller is dismissed. This block contains the instance of the `presentingViewController`. You may specify `nil` for this parameter.
    func dismiss<T: UIViewController>(until reached: T.Type, animated flag: Bool, completion: ((T) -> Void)? = nil) {
        guard let presenting = presentingViewController as? T else {
            return presentingViewController?.dismiss(until: reached, animated: flag, completion: completion) ?? ()
        }

        presenting.dismiss(animated: flag) {
            completion?(presenting)
        }
    }
}

Completely forgot why I made this as it is incredibly stupid logic considering most of the time a modal view controller's presenting view controller is UITabBarController rendering this completely useless. It makes much more sense to actually acquire the base view controller instance and call dismiss on that.

Mark Bourke
  • 9,052
  • 7
  • 21
  • 29
1

For Swift 3.0+

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

This will dismiss all presented view controllers on your rootviewcontroller.

Bhuvan Bhatt
  • 2,526
  • 16
  • 19
1

First of all Oscar Peli thanks for your code.

To start your navigationController at the beginning, you could make it a little more dynamic this way. (in case you don't know the number of ViewControllers in stack)

NSArray *viewControllers = self.navigationController.viewControllers;
[self.navigationController popToViewController: [viewControllers objectAtIndex:0] animated: YES];
Ben Groot
  • 4,915
  • 3
  • 38
  • 44
0

Dismiss the top VC animated and the other ones not. If you hace three modal VC

[self dismissModalViewControllerAnimated:NO]; // First
[self dismissModalViewControllerAnimated:NO]; // Second
[self dismissModalViewControllerAnimated:YES]; // Third

EDIT: if you want to do this only with one method, save you hierarchy into an array of VC and dismiss the last object animated and the other ones not.

emenegro
  • 6,510
  • 9
  • 42
  • 64
  • If I use your code in the last VC, the second call of dismissModalViewControllerAnimated causes a crash: objc[7035]: FREED(id): message dismissModalViewControllerAnimated: sent to freed object=0x4c8e9a0 Program received signal: “EXC_BAD_INSTRUCTION”. – Oscar Peli Jun 02 '10 at 10:16
  • You must do this in each VC, not all in the last one because on the second line you don't have a modal view controller over the current. The best approach can be save your VC hierarchy on an array and dismiss each one not animated but the last. You can do it on you AppDelegate – emenegro Jun 02 '10 at 10:30
  • 1
    You gotta dismiss from the first modal view controller (or it's parent I think) to get this to work. – sunkencity Nov 16 '11 at 14:40
  • Sometimes if you're not using a nav controller this is a really good way of doing it - you need to have the first ones as not animated or the subsequent ones won't be dismissed. Not sure why this was voted down? – SomaMan Mar 29 '12 at 21:06
0

Apple document about dismiss(animated:completion:) method.

In section Discussion, it said:

any intermediate view controllers are simply removed from the stack.

If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.

In other words, if the view controller stack like following

Root -> A -> B -> C -> D ... -> Z

D calls dismiss method, all view controllers behide D, ex: (E ... Z), will be removed from the stack.

AechoLiu
  • 15,710
  • 9
  • 85
  • 113
0

In swift 4 And Xcode 9 This will helps you.

var vc : UIViewController = self.presentingViewController!
        while ((vc.presentingViewController) != nil) {
            vc = vc.presentingViewController!
        }
        vc.dismiss(animated: true, completion: nil)

Enjoy !!! :)

Anup Gupta
  • 1,556
  • 2
  • 22
  • 37
-1

If you're going right back to the start, you can use the code [self.navigationController popToRootViewControllerAnimated:YES];

zeroCube
  • 257
  • 1
  • 3
  • 9
  • 2
    Wrong. He's presenting using `Modal` s, not `Push`es. This would only work if you have a UINavigationController, which usually you don't while using modals. – AhmetB - Google Aug 06 '12 at 12:17
  • 1
    -1: `[self.navigationController popToRootViewControllerAnimated:YES]` will NOT dismiss any presented modal view controllers. – JRG-Developer Feb 08 '13 at 05:24