My View Contollers
Login -> Main Menu -> A -> B -> C -> D
How do i dimiss all view controllers and go back to main menu
For Logout from my view controllers I am doing the following which takes back to Login
func logout{
self.view.window!.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
}
Now what i am doing is this
class AppDelegate: UIResponder, UIApplicationDelegate {
var viewControllerStack: [BaseViewController]!
}
override func viewDidLoad() {
super.viewDidLoad()
super.appDelegateBase.viewControllerStack.append(self)
}
func go_To_MainMenu(){
var countOfNumberOfViewCOntrollers = self.appDelegateBase.viewControllerStack.count
switch countOfNumberOfViewCOntrollers{
self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
break;
case 2:
self.presentingViewController?.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
break;
}
}
If your MainMenu VC always comes AFTER your Login VC, you could simply use the same method:
To MainMenu:
self.view.window!.rootViewController?.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
Instead of presenting/dismissing you can use UINavigationController to push/pop view controllers. That way you can use UINavigationController's popToViewController(_:animated:) which can pop to any view controller in navigation stack.
You can use Unwind Segue if MainMenu isn't your rootViewController. Look at this article, hope it will help.
Unwind Segue with Swift
Swift 5
This works for me
var presentingViewController = PresentingViewController()
self.dismiss(animated: false) {
presentingViewController.dismiss(animated: false, completion: nil)
}
Use unwind segue instead of using RootViewController.
Using unwind you can go back to any ViewController. DismissViewController always send the controller out from NavigationController.
This worked for me,
self.view.window!.rootViewController?.presentedViewController?.dismiss(animated: true, completion: nil)
Related
This question already has answers here:
Dismissing multiple modal view controllers at once?
(7 answers)
Closed 4 years ago.
I recreated the question and describe its essence more precisely.
I have two view controllers presented modally on Swift 4, without storyboard (can't use unwind) and without navigation controller
A presents B which presents C.
The reason why we don't use navigation controller, it's because we what simple animation from bottom to top and instead of breaking the standard animation from right to left, we decided to use present.
I would like to dismiss 2 view controllers and go from C to A.
Please, don't mark this question as duplicate before you read my question carefully. I found a tone of similar post, but neither solved my problem. Some of them Objective-C or some of the suggest to use:
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
Or:
self.presentingViewController?.dismiss(animated: false, completion: nil)
self.presentingViewController?.dismiss(animated: true, completion: nil)
It's works, but it create weird animation. It's just delete C and animate dismiss for B:
Expected result:
Idea: you need to dismiss third controller with animation and after dismissing you need to dismiss second without animation. While third controller is being dismissed, second shouldn't be visible.
First, set Presentation style of second view controller to Over Current Context when you're presenting it (since we will need to hide its view when we will dismiss third controller)
let vc2 = VC2()
vc2.modalPresentationStyle = .overCurrentContext
present(vc2, animated: true)
continue with creating callbacks properties for willDismiss and didDismiss inside third controller. This callback will be called before and after you dismiss third controller
class VC3: UIViewController {
var willDismiss: (() -> Void)?
var didDismiss: (() -> Void)?
#IBAction func dismissButtonPressed(_ sender: UIButton) {
willDismiss?()
dismiss(animated: true) {
self.didDismiss?()
}
}
}
then in second view controller in the place where you present third view controller, set third controller's callback properties: declare what happens when third controller will dismiss: you need to hide view of second and then after dismissing third you need to dismiss second without animation (view can stay hidden since view controller will be deinitialized)
class VC2: UIViewController {
#objc func buttonPressed(_ sender: UIButton) {
var vc3 = VC3()
vc3.willDismiss = {
self.view.isHidden = true
}
vc3.didDismiss = {
self.dismiss(animated: false)
}
present(vc3, animated: true)
}
}
Anyway, second option is using UINavigationController and then just call its method popToViewController(_:animated:).
Create a snapshot from the currently visible view and add it as a subview to the first presented view controller. To find that you can simply "loop through" the presenting view controllers and dismiss from the initial one:
#IBAction func dismissViewControllers(_ sender: UIButton) {
var initialPresentingViewController = self.presentingViewController
while let previousPresentingViewController = initialPresentingViewController?.presentingViewController {
initialPresentingViewController = previousPresentingViewController
}
if let snapshot = view.snapshotView(afterScreenUpdates: true) {
initialPresentingViewController?.presentedViewController?.view.addSubview(snapshot)
}
initialPresentingViewController?.dismiss(animated: true)
}
This is the result with slow animations enabled for the dismissal:
https://www.dropbox.com/s/tjkthftuo9kqhsg/result.mov?dl=0
If you are not using a navigation controller and you want to dismiss all ViewControllers to show the root ViewController (assuming A is the root):
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
Use this in button action method.
The current VC will dismiss when you dismiss the parent VC.
This will dismiss both VCs in single animation.
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
I have a stack of UIViewControllers like A -> B -> C. I want to go back to controller A from C. I'm doing it with below code:
DispatchQueue.global(qos: .background).sync {
// Background Thread
DispatchQueue.main.async {
self.presentingViewController?.presentingViewController?.dismiss(animated: false, completion: {
})}
}
It works but controller B seen on screen although I set animated to false. How can I dismiss two UIViewControllers without showing the middle one (B)?
P.S: I can't just directly dismiss from root controller and also I can't use UINavigationController
I searched the community but can't find anything about the animation.
Dismiss more than one view controller simultaneously
Try this.
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
Created a sample storyboard like this
The yellow view controller is type of ViewController and the button action is as follows
#IBAction func Pressed(_ sender: Any) {
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
}
Output
I've created example for dismissing B controller before showing C controller. You can try it.
let bController = ViewController()
let cController = ViewController()
aController.present(bController, animated: true) {
DispatchQueue.main.asyncAfter(wallDeadline: .now()+2, execute: {
let presentingVC = bController.presentingViewController
bController.dismiss(animated: false, completion: {
presentingVC?.present(cController, animated: true, completion: nil)
})
})
}
But on my opinion solution with using navigation controller would be the best for the case. For example you can put just B controller into navigation controller -> present the navController onto A controller -> then show C inside the navController -> then dismiss from C controller whole navController -> And you will see A controller again. Think about the solution too.
Another solution
I've checked another solution.
Here extension which should solve your problem.
extension UIViewController {
func dissmissViewController(toViewController: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
self.dismiss(animated: flag, completion: completion)
self.view.window?.insertSubview(toViewController.view, at: 0)
dissmissAllPresentedControllers(from: toViewController)
if toViewController.presentedViewController != self {
toViewController.presentedViewController?.dismiss(animated: false, completion: nil)
}
}
private func dissmissAllPresentedControllers(from rootController: UIViewController) {
if let controller = rootController.presentedViewController, controller != self {
controller.view.isHidden = true
dissmissAllPresentedControllers(from: controller)
}
}
}
Usage
let rootController = self.presentingViewController!.presentingViewController! //Pointer to controller which should be shown after you dismiss current controller
self.dissmissViewController(toViewController: rootController, animated: true)
// All previous controllers will be dismissed too,
// but you will not see them because I hide them and add to window of current view.
But the solution I think may not cover all your cases. And potentially there can be a problem if your controllers are not shown on whole screen, all something like that, because when I simulate that transition I don't consider the fact, so you need to fit the extension maybe to your particular case.
I have UINavigationController with several pushed view controllers.
UPD: Last pushed controller modally presents another controller.
Also, I have UINavigationControllerDelegate with some logic at navigationController:willShowViewController:animated:.
UPD: Navigation controller is its own delegate. Delegate is set in viewDidLoad method.
Question rises when I try to close all controllers programically from presented view controller:
// Close all controllers in navigation stack
presentingViewController?.navigationController?.popToRootViewController(animated: true)
// Close presented view controller
dismiss(animated: true, completion: nil)
Method navigationController:willShowViewController:animated: is not called. But it is called when I do the same without presented controller (thanks to #donmag for example project where it works).
Searched SO for answers or similar questions, but found nothing, any thoughts?
In your "presenting" VC, you want to implement a delegate/protocol pattern so your "presented" VC can call back and perform the dismiss and popToRoot...
// protocol for the presented VC to "call back" to the presenting VC
protocol dismissAndPopToRootProtocol {
func dismissAndPopToRoot(_ animated: Bool)
}
// in the presenting VC
#IBAction func presentTapped(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "presentMeVC") as? PresentMeViewController {
// Assign the delegate when instantiating and presenting the VC
vc.dapDelegate = self
present(vc, animated: true, completion: nil)
}
}
func dismissAndPopToRoot(_ animated: Bool) -> Void {
// this will dismiss the presented VC and then pop to root on the NavVC stack
dismiss(animated: animated, completion: {
self.navigationController?.popToRootViewController(animated: animated)
})
}
// in the presented VC
var dapDelegate: dismissAndPopToRootProtocol?
#IBAction func dismissTapped(_ sender: Any) {
// delegate/protocol pattern - pass true or false for dismiss/pop animation
dapDelegate?.dismissAndPopToRoot(false)
}
Here's a full demo project: https://github.com/DonMag/CustomNavController
From documentation:
popToRootViewControllerAnimated:
Pops all the view controllers on the stack except the root view controller and updates the display.
popViewControllerAnimated:
Pops the top view controller from the navigation stack and updates the display.
So seems like in order to get navigationController:willShowViewController:animated: called every time you have to do subsequent popViewControllerAnimated:, because the display got updated each time after you pop a new controller. When you pop to root view controller, update display is called only once.
Let say I have the following VCs:
RootVC --> VC A --> VC B
I'm using present method to present view controller from RootVC to VC A then to VC B. Now I'm on VC B and I want to dismiss from VC B back to RootVC using
self.view.window!.rootViewController?.dismiss(animated: true, completion: nil)
it works but I still see VC A shows up during the dismiss process. Then, I try this method
self.presentationController?.presentedViewController.dismiss(animated: true, completion: nil)
It also works to dismiss back to root VC but I still see VC A in process.
My question is is there a way to not show VC A during the dismiss process? I already try animated: false but still get the same result. Thanks!
You need to make the change in the modalPresentationStyle to the .custom. The custom will allow you to view the presentingViewController view when the current visible controller's view is transparent.
Now when you want to go back to root view on the current presenting stack you need to call the method dismissToRootViewController(animated: completion:).
In the implementation of this method will allow all intermediate presenting view controller view to be transparent which will give you dismiss animation from VC c to RootVC.
extension UIViewController {
func dismissToRootViewController(animated: Bool, completion: (() -> Swift.Void)? = nil) {
var viewController = self.presentingViewController
while viewController?.presentingViewController != nil {
viewController?.view.alpha = 0.0
viewController = viewController?.presentingViewController
}
self.dismiss(animated: true) {
viewController?.dismiss(animated: false, completion: completion)
}
}
}
You can try to use this:
navigationController?.popToRootViewController(animated: false)
My first view controller has a button, which triggers the #IBAction goTo2ndVc() which presents a second ViewController:
class FirstVC: UIViewController {
...
#IBAction func goTo2ndVc() {
let secondVc = SecondVC(label: "I am second vc.")
self.presentViewController(secondVc, animated: true, completion: nil)
}
When the button is pressed, the 2nd view controller is shown on screen. No problem.
In 2nd view controller, there is also a button which is used to go back to 1st view controller:
class SecondVC: UIViewController {
...
#IBAction func backToFirst(sender: AnyObject) {
print("go back ...")
self.navigationController?.popViewControllerAnimated(true)
}
}
I looked on internet, people suggest to use navigationController?.popViewControllerAnimated(true) to go back to previous controller. But when I press the go back button I can see the print message "go back ..." but the app doesn't go back to 1st view controller. WHY?
#IBAction func backToFirst(sender: AnyObject) {
print("go back ...")
self.dismissViewControllerAnimated(true, completion: nil)
}
In Swift 3
self.dismiss(animated: true, completion: nil)
you should not use navigation controller, because you didn't use it when you were adding the second view controller. that's why simply call dismissViewControllerAnimated method.
You have to use UINavigationController and its pop methods only when you add your view controllers via pushViewController method.
Familiarize yourself with the concept of navigation controller here: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/NavigationControllers.html
there
the issue is very simple..
self.presentViewController(secondVc, animated: true, completion: nil)
the code will present second view, you are not pushing it.
self.navigationController?.popViewControllerAnimated(true)
the popViewController will pop back to the previous view controller from where it is been pushed.
So, there are two ways you can achieve what you want
1)If you want to present viewController then you have to dismiss the view controller to show previous view controller with
self.dismissViewControllerAnimated(true, completion: nil)
2)If you want to use PopToVewcontroller, then you have to push you second view controller instead of presenting it with
self.navigatioVonroller?.pushViewController(secondVc, animated: true)
If you want to return to the previous view controller, you can simply add:
[self dismissViewControllerAnimated:YES completion:nil];
to the button action method.
If this is added on the nav view controller present on every screen, I see no reason why it shouldn't work as it would always dismiss the most recently presented view.