How to check if a view controller has been dismissed in Swift - ios

If I present a ViewController like so:
let authViewController = authUI!.authViewController()
authViewController.modalPresentationStyle = .overCurrentContext
self.present(authViewController, animated: true, completion: nil)
I would like to know when the ViewController has been dismissed. I have tried the following:
let authViewController = authUI!.authViewController()
authViewController.modalPresentationStyle = .overCurrentContext
self.present(authViewController, animated: true, completion: {
print("View Dismissed")
})
but that only lets me know if the view was presented successfully. This ViewController was not created by me so I can't change the viewWillDissapear method.

Whole answer is predicated on an assumption that OP doesnt have access to authViewController code
If you dont have access to authViewController code, lousy solution would be to use viewWillAppear of your view controller to find when auth view controller is dismissed.
Basically when you present/push any viewController over your existing view controller, your view controller's viewWillDisappear will be called similarly when presented/pushed view controller is dismissed, or popped out viewWillAppear will be called.
Because viewWillAppear might get called for other reasons as well and you wouldnt wanna confuse it as authViewController dismiss, use a boolean
private var shouldMonitorAuthViewControllerDismiss = false //declared a instance property
Set the boolean to true when you actually present the authViewController
let authViewController = authUI!.authViewController()
authViewController.modalPresentationStyle = .overCurrentContext
self.present(authViewController, animated: true, completion: {
shouldMonitorAuthViewControllerDismiss = true
})
Finally in your viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
if shouldMonitorAuthViewControllerDismiss {
//auth view controller is dismissed
}
shouldMonitorAuthViewControllerDismiss = false
}

Related

Swift: Presenting VC in completion handler of dismissal

I have the following ViewController (VC) flow:
SplashScreen/launch VC -> Login VC (on notification, present) -> SplashScreenVC -> Main VC
I want to avoid using an unwind segue because I will need to regularly re-authenticate the user regardless of current VC and so would much rather programatically 'present'.
The problem is, I am able to present and dismiss the SplashScreen VC (which is originally the root) but then cannot do the same for the Login VC without an error.
Code:
//in SplashScreen VC viewDidAppear
let loginVC = myStoryboard.instantiateViewController(identifier: "loginVC") as UIViewController
loginVC.modalPresentationStyle = .fullScreen
loginVC.modalTransitionStyle = .coverVertical
//dismissal?
self.dismiss(animated: true, completion: {
self.present(loginVC, animated: true, completion: nil)
})
//in loginVC selector function
let launchVC = myStoryboard.instantiateViewController(identifier: "launchVC") as UIViewController
launchVC.modalPresentationStyle = .fullScreen
launchVC.modalTransitionStyle = .coverVertical
//check for top view controller (debugging)
print("TOPVC at LoginVC: \(self.getTopVC()!)")
//handle dismissal?
self.dismiss(animated: true, completion: {
self.present(launchVC, animated: true, completion: nil)
})
WARNING:
Warning: Attempt to present <Slidr.LaunchScreenViewController: 0x15be0ef90> on <Slidr.LoginViewController: 0x15be6b510> whose view is not in the window hierarchy!
Warning: Attempt to present <Slidr.TestingViewController: 0x15db00ac0> on <Slidr.LaunchScreenViewController: 0x15bd06960> whose view is not in the window hierarchy!
Code runs fine if I don't dismiss the loginVC but I would like to avoid residue controllers over time.
I've tried to present from the top VC rather than 'self' but that doesn't seem to change anything.
Any help would be much appreciated.
As error says you need to present a vc from a 1 that's currently dismissed , so instead do
self.dismiss(animated: true, completion: {
(UIApplication.shared.delegate as! AppDelegate).window?.rootViewController = loginVC
}
Another way also is to embed rootVC inside a navigationController and do
self.navigationController?.setViewControlls([loginVC],animated:true)

When calling a function to switch to another ViewController, the application crashes

Error text:
Application tried to present modally an active controller.
Moreover, all elements seem to be called for the first time. The application crashes most likely due to the navigation controller. How can I fix this error?
#objc private func taskButtonTouched(){
let vc = TaskVC()
let navigationController = OptionalNC(rootViewController: vc)
navigationController.modalPresentationStyle = .fullScreen
self.present(vc, animated: true, completion: nil)
}
You must present OptionalNC insetead TaskVC
#objc private func taskButtonTouched(){
let vc = TaskVC()
let navigationController = OptionalNC(rootViewController: vc)
navigationController.modalPresentationStyle = .fullScreen
self.present(navigationController, animated: true, completion: nil)
}

Swift - Dismiss ViewController Going Too Far Back

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)
}

Swift How to present view in root navigation after dismiss modal

I'm trying to make ViewController present after Modal dismiss
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let chatRoomVC = storyboard.instantiateViewController(withIdentifier: "ChatRoomVCId") as! ChatRoomVC
chatRoomVC.hidesBottomBarWhenPushed = true
chatRoomVC.passValue = passValue
self.dismiss(animated: true, completion: {
self.present(chatRoomVC, animated: true, completion: nil)
})
But it will return "whose view is not in the window hierarchy!" maybe it's present a view after the controller dismissed
Notice the self in self.present, what you are doing, is basically tell the vc that you are dismissing to present a new vc, thats wrong way to do, the correct way is tell it's PARENT vc to present a new vc, by using delegate/unwind to call the parent vc to present new vc
You are using self. It means you are dismissing the current view controller. It should be the parent view controller who will present a new view controller.
When present from "Controller_A" to "Controller_B"-> present it like given below
--- View Controller A ---
self.navigationController?.present(Controller_B, animated: true, completion: nil)
When you want to dismiss "Controller_B" and present "Controller_C" using Navigation controller
--- View Controller B ---
let presenController : UINavigationController = self.presentingViewController as! UINavigationController
presentingController.dismiss(animated: true, completion: {
presenController.pushViewController(Controller_C, 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