How to dismass modal viewcontroller without navigationcontroller? - ios

I have two UIViewControllers. There is no embedded navigationcontroller. They are connected by a segue, which is a "Present Modally".
I have a button on scene2. When it is clicked, I call this code:
self.navigationController?.popViewControllerAnimated(true)
I can see the code is hit but nothing happens. What am I doing wrong?

What you are doing wrong is that you are popping a view controller from a non existing navigation controller.
Basically when you say self.navigationController? that returns nil because there is no navigation controller. So the pop function doesn't get called.
What you have to do is call self.dismissViewControllerAnimated(true, completion: nil).

I think that you have to dismiss your controller and not pop it to back action:
self.dismissViewControllerAnimated(true, completion: {});

Related

Swift after dismiss Current ViewController then the Presenting ViewController not calling viewWillAppear()

In my case, I am using two view controller VC1 and VC2. Here, VC1 button click to Present Modally and Over Full Screen presentation with Cross Dissolve Transition to presenting VC2. Now, from VC2 dismiss then I didn’t get call VC1 viewWillAppear().
I am not using code base for Present model. I am using Storyboard Segue.
Why it happening and how to fix this?
From Docs,
Note
If a view controller is presented by a view controller inside of a
popover, this method is not invoked on the presenting view controller
after the presented controller is dismissed.
So according to the documentation when a ViewController presents another ViewController modally this method will not be called. To fix this you need to use
func dismiss(animated flag: Bool,
completion: (() -> Void)? = nil)
and move(or repeat) some of viewWillLoad logic to completion handler.
Change presentation to Full screen or If you want to stick to Over Full Screen then make vc2 delegate of vc1 and call delegate method on dismiss.
To understand the concept you can refer to : https://medium.com/livefront/why-isnt-viewwillappear-getting-called-d02417b00396

dismiss viewController always trigger viewDidLoad and never viewWillAppear

I have a tab bar, in the last tab I have a UIViewController A, that have a button inside, and when you tap on it it is presenting an UIViewController B :
let bVC = B()
bVC.settingsPresenter = self
self.present(bVC, animated: true, completion: nil)
When the user is in B and wants to get out of the screen, there is a button that calls this method on A (not B) :
self.dismiss(animated: true, completion: nil)
So when I arrive on the tab for the first time, it calls viewDidLoad then viewWillAppear on A.
If I go to the first tab then come back to the last, only viewWillAppear on A. Everything looks normal.
But when I dismiss B, viewDidLoad of A is called, and not even viewWillAppear... how can I make it the inverse (you know the logical way as the view is already loaded and I only need to be notify that the tab will appear)
if the ViewController is loading but showing another one, it will never view will appear as the other view controller it is presenting will appear
Hope this helps!

testing "presentingViewController is UIViewController" works fine in one case, fails in other

Following Apple's documentation for adding and editing information Apple guide here I have a Viewcontroller with a tableview. The tableview contains a header with a "Add new" Button. If a table row is selected the detailViewController is pushed onto the stack. The detailViewController is also embedded in a UINavigationController, as in Apple's docs. If "Add new" is pressed, another segue is performed which presents the UINavigationController modally, which in turns shows the detailViewController. This works fine and the animation clearly shows a modally presented ViewController.
The detailViewController contains a Cancel Button in the NavigationBar. If it is pressed the following code is run:
#IBAction func cancel(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
var isPresentingInAddActionMode = false
if let presentingVC = self.presentingViewController{
isPresentingInAddActionMode = presentingVC is UINavigationController
}
streekgidsModel.undoManager.endUndoGrouping()
print("undo grouping ended and undone")
streekgidsModel.undoManager.undo()
if isPresentingInAddActionMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The MealViewController is not inside a navigation controller.")
}
}
The first if-statement checks if the property presentingViewController is present, and if so if it is of type UINavigationController. If so, the viewController is presented modally and should be dismissed. If not it is pushed onto the stack and the owningNavigationController should pop the detailViewController.
Running this code does not work as described by Apple. The check on the presentingViewController shows it is present, but the type check gives back "invalid". This is treated as false. The test on the owningNavigationController succeeds (I think it should fail) and the popViewController is executed. As there was no push, the view controller is not popped or dismissed and is still visible. A second press on Cancel executes the func cancel again, which results in an error as there is no longer a group started in the undo manager.
Baffling thing is that I have the same code in another viewcontroller, with similar UIViewTable and navigation and it works fine.
So to frame the question: why does this not work the way Apple describes it, why does my other view controller work as it is supposed to? Any input is appreciated.
BTW, the fatal error text is straight from the docs so the naming is not relevant and it is never executed.
I would start with checking who is presenter.
According to Apple docs on this:
When you present a view controller modally (either explicitly or implicitly) using the present(_:animated:completion:) method, the view controller that was presented has this property set to the view controller that presented it. If the view controller was not presented modally, but one of its ancestors was, this property contains the view controller that presented the ancestor. If neither the current view controller or any of its ancestors were presented modally, the value in this property is nil.
If the docs are correct then your presenter should be your "Viewcontroller with a tableview" which, I guess, is not UINavigationController. If that is the case then you should understand why your code fails.
It depends on your context of course, but I would just simplify a check this way:
var isPresentingInAddActionMode = self.presentingViewController != nil
... // your other code
if isPresentingInAddActionMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
If I understood your question and intent correctly then it doesn't matter for you who (which class) presented your detailVC and you care only about how your detailVC was presented - either pushed in navigation view controller or presented modally. I think just by checking presentingViewController property you can get that information.

Unable to go back through a navigation controller to a VC of another navigation controller using Swift

As you can see below, the notificationsVC is a part of the TabBarController which is embedded in a navigationContoller(lets call it first nC). Then theres a segue from notificationsVC to the second navigationController which will show the messagesVC.
There's a back button in messagesVC which when pressed should go back to notificationsVC
func backbutton() {
navigationController?.popViewControllerAnimated(true)
}
Now this is obviously not working because the navigationController will get the nearest NC and pop the VC in its stack but it won't let me go back to the notificationsVC.
Any other alternative?, although I've tried this with no success as well.
self.dismissViewControllerAnimated(true, completion: nil);
More detailed view
Also I'm using the JSQMessagesViewController library to show the messages in messagesVC which shouldn't matter but still worth mentioning. Thanks for your time!
You can access first NavigationViewController by asking it from TabBarViewController like in code below:
tabBarController?.navigationController?.popViewControllerAnimated(true)
Also asking navigation controller from you second navigation controller should work:
navigationController?.navigationController?.popViewControllerAnimated(true)
Your Navigation controller has only one VC i.e MessagesVc. So when you pop it,there is no other VC in the Navigation Controller's stack which can be presented. Your NotificationsVC is not in the Navigation controller's stack.
So I suggest you to do like this on back button click:
tabBarController?.selectedIndex = Index_Of_NotificationsVC
Try : -
let nVC = self.navigationController?.tabBarController?.navigationController?.storyboard?.instantiateViewControllerWithIdentifier("NotificationStoryboardVC_ID") as! NotificationVC
navigationController?.tabBarController?.navigationController?.pushViewController(nVC, animated: true)

How to return to Root View Controller

I am currently working on an app which presents a screen modally and another custom progress indicator modally. Is it possible to return to the root View Controller seamlessly?
Home -> screen1-> screen2(Custom progressIndicator)
I want to dismiss the custom progressIndicator (and the screen presented modally) and return to my home (root) View Controller in one go.
self.navigationController?.popToRootViewControllerAnimated(true)
Thank you for the help!
You need to dismiss presented model then you can pop all the pushed view controllers. As presented model would not be in the stack of the navigation.
self.dismissViewControllerAnimated(true, completion: {});
Then you can pop to base view controller.
self.navigationController.popViewControllerAnimated(true);
If you use presentViewController, you can use
[self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
to go back to root view. It works on IOS9.
Swift 4
self.dismiss(animated: true, completion: {});
self.navigationController?.popViewController(animated: true);
In Swift 3 this will work:
self.dismiss(animated: true, completion: {});
self.navigationController?.popViewController(animated: true);

Resources