Swift: Presenting VC in completion handler of dismissal - ios

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)

Related

Swift - popViewController after Dismiss

I'm trying to dismiss viewcontroller presented as modal("Present Modally") and then popViewController in navigationController. Here is my storyboard, I want to go from QRScanner VC to Monitoring VC.
Here is how I'm presenting QRScanner VC in Add Device VC:
let storyboard = UIStoryboard(name: StoryboardIDs.MainStoryboard, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: VCIDs.QRDeviceScannerVC) as! QRDeviceScannerVC
controller.deviceName = deviceNameTxt.text
present(controller, animated: false, completion: nil)
Here is how I'm trying to go back to MonitoringVC:
self?.navigationController?.popViewController(animated:true)?.navigationController?.popViewController(animated: false)
Also tried:
self?.dismiss(animated: true, completion: {
self?.presentingVC?.navigationController?.popViewController(animated: false)
})
It always goes to the Add Device VC instead of Monitoring VC
Use an unwind segue, with the unwind target method located in the Monitoring VC. All the right stuff will just happen as if by magic.
You can try with
self?.dismiss(animated: false, completion:nil)
self?.presentingVC?.navigationController?.popViewController(animated: true)
Just check if you have reference to the navigation controller

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.

React Native on iOS crashed while calling native module and try to present ViewController

When trying to present a ViewController, on native modules it's crash.
Here is the code on the native side.
#objc(SomethingManager)
class SomethingManager : NSObject {
#objc(doPresent)
func doPresent() {
let vc = UIViewController()
let navigationController = UINavigationController(rootViewController: vc)
navigationController.setNavigationBarHidden(true, animated: false)
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
rootViewController?.dismiss(animated: false, completion: nil)
rootViewController?.present(vc, animated: true, completion: nil)
}
}
the crash said
'Application tried to present modally an active controller <UIViewController: 0x7fb09441e620>`
I've tried to call it on main thread, without any luck
You are trying to present vc, which is already embedded in your navigationController. You should therefore be presenting navigationController and not vc.
rootViewController?.present(navigationController, 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