Swift - Dismiss ViewController Going Too Far Back - ios

When I click on cancel button in a modal pop-up ViewController, for some reason it goes to the very beginning of the navigation stack, instead of back to thew viewController that called it. Am I doing something wrong?
Root of the stack, calling ViewControllerA:
class RootViewController {
let viewController = ViewControllerA(contact: selectedContact)
self.navigationController!.pushViewController(viewController, animated: true)
}
ViewControllerA / Calling the modal:
let viewController = ViewControllerB()
let navigationController = UINavigationController(rootViewController: viewController)
self.present(navigationController, animated: true, completion: nil)
ViewControllerB / Dismissing the modal:
#objc func cancelButtonPressed() {
self.dismiss(animated: true, completion: nil)
}

Related

Navigate to ViewController and dissmiss VC

Here i'm dismissing one VC and trying to navigate new VC , but navigation not working. Dismiss VC working fine.
#IBAction func onClickEmailBtn(_ sender: UIButton) {
//dismiss VC
self.dismiss(animated: false, completion: nil)
//Navigate to VC
DispatchQueue.main.async {
let cevc = self.storyboard?.instantiateViewController(withIdentifier: "CEVC") as! EmailViewController
self.navigationController?.pushViewController(cevc, animated: true)
}
}
Here I have one NavigationViewController called VC1, on that I'm presenting one VC called VC2. In this VC2 when I click button I want to navigate new VC called EmailVC.
try this code
#IBAction func onClickEmailBtn(_ sender: UIButton) {
if let controller = self.storyboard?.instantiateViewController(withIdentifier: "CEVC") as! EmailViewController {
self.dismiss(animated: false, completion: nil)
self.presentingViewController?.present(controller, animated: true, completion: nil)
}
}
You were trying to push a ViewVontroller from dismissing view controller,
and the answer which you have accepted is presenting a viewController from dismissing viewController. Although it may serve your purpose, but I am going to answer you original question
// get the object of presenting nanvigation controller(navigation controller with has presented this view controller)
let presentingNavigationController = presentingViewController?.navigationController
dismiss(animated: true) {
let cevc = self.storyboard?.instantiateViewController(withIdentifier: "CEVC") as! EmailViewController
// here you push your view controller with presentingNavigationController
presentingNavigationController?.pushViewController(cevc, animated: true)
}
Try to find out Navigation controller using below code and replace Viewcontroller with your Controller name.
let productVC = SeconViewViewController()
let parentVC = (parent!.presentingViewController as! ViewController).childViewControllers.first as! UINavigationController
self.navigationController?.dismiss(animated: true, completion: {
parentVC.pushViewController(productVC, animated: true)
})

Popping or dismissing a ViewController

I have a Feedback View Controller that is accessed in 7 locations across 4 different screens.
One way it's presented in a navigationController via pushViewController. The other 6 times it's presented modally.
Here's the function that opens the Feedback VC's
struct Constants{
static func openFeedback(openFrom: UIViewController, nav:Bool) {
let fbStoryboard = UIStoryboard(name: "FeedbackViewController", bundle: nil)
let fbVC = fbStoryboard.instantiateViewController(withIdentifier: "FBSBID")
fbVC.modalPresentationStyle = .overFullScreen
fbVC.modalTransitionStyle = .crossDissolve
if nav {
openFrom.navigationController?.pushViewController(fbVC, animated: true)
} else {
openFrom.present(fbVC, animated: true, completion: nil)
}
}
}
The Feedback VC is called with either Constants.openFeedback(openFrom: self, nav: true) or Constants.openFeedback(openFrom: self, nav: false)
Opening the VC works just fine!
Here's my close button on the Feedback View Controller:
#IBAction func closeButtonPressed(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
Which works 6 out of the 7 times, when not in the navigation stack. When it's in the navigation stack, the close button does not do anything.
My question is, how do I close out of the Feedback View Controller, based on if it's in the navigationController stack or not?
You can simply check if view controller is embedded inside UINavigationController by checking if controller's navigationController is nil or not.
So if it is embedded you can use popViewController(animated:) on navigation controller to "dismiss" pushed controller
if let navCon = navigationController {
navCon.popViewController(animated: true)
} else {
dismiss(animated: true)
}

Dismiss second ViewController from the third ViewController

I am trying to dismiss VC b from VC c where VC c is a popover and has a button for sign out but it is not working.
The structure of the flow is
VC a ----presents modally----> VC b ----presents a popover----> VC c
When the button in the popover is clicked the VC c and VC b must be dismissed so that (VC a)ViewWillAppear is called.
Try this:
You can dismiss your presentingViewController from child view controller as follow
self.presentingViewController?.dismiss(animated: true, completion: nil)
When you add a ViewController as childViewController
self.parent?.dismiss(animated: true, completion: nil)
If this view controller is a child of a containing view controller (e.g. a navigation controller or tab bar
controller,)
weak open var parent: UIViewController? { get }
The view controller that was presented by this view controller or its nearest ancestor.
open var presentedViewController: UIViewController? { get }
The view controller that presented this view controller (or its farthest ancestor.)
open var presentingViewController: UIViewController? { get }
If ViewControllers have hierarchy like
VC a ----presents as self.present(objects, animated: true, completion: nil) modally----> VC b ---- presents as self.present(objects, animated: true, completion: nil) popover----> VC c
And there are a button on VC c to move back to VC a then you can use:
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
First of all try to dismiss VC b from itself, not presenting the VC c just to check if it works, using: self.dismiss(animated: true, completion: nil) or if VC b is embedded in a navigation controller, like this: self.navigationController?.dismiss(animated: true, completion: nil)
If the one from above works, I would suggest you implement the delegation protocol, where VC c will delegate to VC b the dismissal, whenever it should be done. You could also use a completion block for that, containing the dismiss code.
Hope this works
// Call inside View controller C
self.presentingViewController?.dismissViewControllerAnimated(false, completion: nil)
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
`protocol ModalHandler {
func modalDismissed()
Class SecondViewController: UIViewController, ModalHandler {
func modalDismissed() {
self.dismiss(animated: false, completion: nil)
}
func open3rdController() {
let thirdVC = ThirdViewController(_ )
thirdVC.delegate = self
self.present(thirdVC, animated: true, completion: nil)
}
class ThirdViewController: UIViewController {
func dismiss() {
self.delegate.modalDismissed()
}
}
`
Whats about a Presenter or a Coordinator.
This instance will initialize all these VCs and also present them.
From there you can also dismiss them.

Present ViewController with Scene

I have a game menu with button start game:
#IBAction func startGame(_ sender: AnyObject) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "gameViewController") as? GameViewController {
vc.modalTransitionStyle = .crossDissolve
self.present(vc, animated: true, completion: nil)
}
}
Game over code:
if lifes == 0 {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "mainMenuViewController") as? MainMenuViewController {
vc.modalTransitionStyle = .crossDissolve
self.present(vc, animated: true, completion: nil)
}
}
When user click on button i show new view controller with Sprite Kit scene. But when game is over i go back to menu. And if we click Start game again, fps falls from 60(in my case) to 30, then if again to 20 etc. Seems like old view controller still working. How to dismiss it ?
I read similar questions, but didn't find answer in them.
Ok, now your issue is clear.
You should not present a new MainViewController when game is over. This will lead you to potentially infinite stack of view controllers like this:
Main -> Game -> Main -> Game -> ...
Instead you should dismiss your Game vc and return to the previous controller, so that you will always have one or two controllers in memory.
So you should replace this:
if lifes == 0 {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "mainMenuViewController") as? MainMenuViewController {
vc.modalTransitionStyle = .crossDissolve
self.present(vc, animated: true, completion: nil)
}
}
With this:
if lifes == 0 {
dismiss(animated: true, completion: nil) //will bring you to previous Main vc
}
Edit:
If you have more than two controllers to show, you should consider navigation controller approach. Basically you create it with rootViewController (MainVC), then push GameVC, then push GameOver.
In MainVC:
self.navigationController?.pushViewController(gameVC, animated: true)
In GameVC:
self.navigationController?.pushViewController(gameOverVC, animated: true)
To pop only only one controller:
self.navigationController?.popViewController(animated: true)
To pop to the first controller:
self.navigationController?.popToRootViewController(animated: true)

How to correctly dismiss a UINavigationController that's presented as a modal?

In my TabBarViewController, I create a UINavigationController and present it as a modal.
var navController = UINavigationController()
let messageVC = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController
self.presentViewController(self.navController, animated: false, completion: nil)
self.navController.pushViewController(messageVC, animated: false)
Inside my MessageViewController, this is how I want to dismiss it:
func swipedRightAndUserWantsToDismiss(){
if self == self.navigationController?.viewControllers[0] {
self.dismissViewControllerAnimated(true, completion: nil) //doesn't deinit
}else{
self.navigationController?.popViewControllerAnimated(true) //deinits correctly
}
}
deinit{
print("Deinit MessagesViewController")
}
The problem is that when I get to the root View Controller and try to dismiss both the child and the UINavigationController, my MessagesViewController deinit does not get called. Something's holding on to it -- most likely UINavigationController
Your controller hierarchy looks like this:
UITabViewController
|
| presents
|
UINavigationController
|
| contains view controllers
|
[root, MessagesViewController]
Now, if you are inside MessagesViewController, then its navigationController is the one that is being presented and that's the one you should be dismissing but calling dismiss on MessagesViewController should work too.
However, the problem is that dismissing the navigation controller won't remove its view controllers. It seems you are holding to your navigation controller (since you are presenting it using self.navController) so the state will become
UITabViewController
|
| self.navController holds a reference to
|
UINavigationController
|
| contains view controllers
|
[root, MessagesViewController]
To properly destroy MessagesViewController you will have to either let go of the navController or you will have to pop to root (thus removing MessagesViewController from view hierarchy).
The typical solution would be not to save a reference to navController at all. You could always create a new UINavigationController when presenting.
Another solution is using a delegate - instead of dismissing from inside MessagesViewController, let it call back to the presenter, which would call
self.navController.dismiss(animated: true) {
self.navController = nil
}
Try this
func swipedRightAndUserWantsToDismiss(){
self.navigationController.dismissViewControllerAnimated(false, completion:nil);
}
You can use the following to correctly dismiss a UINavigationController that's presented as a modal in Swift 4:
self.navigationController?.popViewController(animated: true)
if you want to just present a viewcontroller, then directly you can present that viewcontroller and no need to take a navigation controller for that particular viewcontroller.
But when we need to navigate from that presented view controller then we need to take a view controller as a root view of navigation controller. So that we can navigate from that presented view controller.
let messageVC = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController
let MynavController = UINavigationController(rootViewController: messageVC)
self.presentViewController(MynavController, animated: true, completion: nil)
and from that presented view controller, you can push to another view controller and also pop from another view controller.
And from presented view controller, here messageVC, we have to dismiss that as
func swipedRightAndUserWantsToDismiss() {
self.dismiss(animated: true, completion: nil)
}
which will dismiss messageVC successfully and come back to origin viewcontroller from where we have presented messageVC.
This is the right flow to perform presentViewController with navigation controller, to continue the navigation between the view controllers.
And for more if you are not sure that messageVC is presented or pushed, then you can check it by this answer.
And the swift version to check that is
func isModal() -> Bool {
if((self.presentingViewController) != nil) {
return true
}
if(self.presentingViewController?.presentedViewController == self) {
return true
}
if(self.navigationController?.presentingViewController?.presentedViewController == self.navigationController) {
return true
}
if((self.tabBarController?.presentingViewController?.isKindOfClass(UITabBarController)) != nil) {
return true
}
return false
}
So our final action to dismiss is like
func swipedRightAndUserWantsToDismiss() {
if self.isModal() == true {
self.dismiss(animated: true, completion: nil)
}
else {
self.navigationController?.popViewControllerAnimated(true)
}
}
No need to have member for navController. Use following code to present your MessagesViewController.
let messageVC = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController
let pesentingNavigationController = UINavigationController(rootViewController: messageVC)
self.presentViewController(pesentingNavigationController, animated: true, completion: nil)
Your dismiss view controller code will be
func swipedRightAndUserWantsToDismiss() {
self.navigationController.dismiss(animated: true, completion: nil)
}
I suggest you use the other initializer for your UINavigationController:
let messageVC = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController
let navController = UINavigationController(rootViewController: messageVC)
self.presentViewController(self.navController, animated: true, completion: nil)
To dimiss, simply do
func swipedRightAndUserWantsToDismiss() {
self.navigationController.dismissViewControllerAnimated(true, completion: nil)
}
This is how I solve the problem in Objective C.
You can call dismissViewControllerAnimated:NO on your self.navigationController itself.
Objective C
[self.navigationController dismissViewControllerAnimated:NO completion:nil];
Swift
self.navigationController.dismissViewControllerAnimated(false, completion: nil)
In Swift 3 this is achieved with:
self.navigationController?.dismiss(animated: true, completion: nil)

Resources