I have a long View Controllers hierarchy;
in the first View Controller I use this code:
SecondViewController *svc = [[SecondViewController alloc] i
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]]
.
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.
- (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];
}
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)
}
}
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)
If you're going right back to the start, you can use the code [self.navigationController popToRootViewControllerAnimated:YES];
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)
}
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];