This problem has been answered using objective C and tab view controllers. I can't figure out how to solve this problem of presenting modally to an active controller when the app tries to return to the home screen.
The series of view controllers for making a post is launched by a button on the top of my app's home screen. All view controllers are presented with the method: present(viewController, animated: true, completion: nil).
I read here that I have to dismiss the view controller that is trying to bring the user back to the home screen. If I write a line of code for dismissing that view controller, it brings me to the view controller immediately preceding it, which is not the home screen. How do I get my app to present the home screen from the end of a series of view controller for making a post without triggering this error?
I know that this has been previously answered in different contexts, but they didn't appear able to help me solve this problem. Would greatly appreciate some fresh eyes on this to help me solve this.
You can dismiss all view controllers instead of showing home screen over them.
var controller = presentingViewController
while let presentingVC = controller?.presentingViewController {
controller = presentingVC
}
controller?.dismiss(animated: true)
Related
My app starts off with a Tab Bar Controller on the first screen after logging in:
Later on the user can tap to get back to that screen, by pressing the Home button. The code for that is:
self.present(self.mealPlanViewController, animated: true, completion: nil)
The issue is that when they go back to that view controller, the Tab Bar is no longer there.
How can I present the view controller with the Tab Bar again?
You said:
"Later on the user can tap to get back to that screen, by pressing the Home button. The code for that is:"
self.present(self.mealPlanViewController, animated: true, completion: nil)
That is wrong. You are not "getting back" to anything. You are presenting self.mealPlanViewController modally. That means it is being drawn on top over everything else. If self.mealPlanViewController was previously on-screen, bad things may happen.
In order to help you sort it out, you need to explain your view controller hierarchy and the flow between screens. What is the name of your tab bar controller's class? Of the view controllers that are displayed as tabs? How are you getting from the tab bar controller to the screen that contains the home button?
I was able to figure it out - please see below:
I added #IBAction func unwindToContainerVC(segue: UIStoryboardSegue) {} to the view controller I wanted to go back to. This added an Action Segue.
I added the segue to the storyboard and gave it the identifier, "unwindToMealPlanViewController"
I added the following code to my Done button: self.performSegue(withIdentifier: "unwindToMealPlanViewController", sender: nil)
These were some very helpful resources in solving this:
How to perform Unwind segue programmatically?
https://www.hackingwithswift.com/example-code/uikit/how-to-perform-a-segue-programmatically-using-performsegue
https://www.youtube.com/watch?v=ULd2v4mHyQ4
I have a bunch of views and then a logout button, which logs the user out and takes them to the first view controller (a login/register screen). I tried doing this with a modal presentation, but it destroys my navigation, and I can't use a pop to root view controller because it is not the root view controller - I am at least 2 navigation controllers deep. How would I go about somehow displaying only the first one? I basically need it to act as if the app was just relaunched. Would unwind segues help in some way? Thanksthis is what I mean by messing up the navigation. The following view controllers now pop up, instead of (the following pic is from the actual first time launching the app) how it should look
Supposing that you use a UINavigationController, you can use the UINavigationController.setViewControllers(_:animated:) method in combination with UINavigationController.viewControllers. The latter provides you an array of view controllers in the exact order they are stacked by the navigation controller, while the former can be used to alter the navigation stack. For instance, if you want to keep only the first 3 view controllers in the navigation stack:
guard let navigationController = self.navigationController else { return }
let viewControllersToKeep = navigationController.viewControllers.prefix(3)
navigationController.setViewControllers(viewControllersToKeep, animated: true)
Note: The animation performed by the navigation controller in this case is usually (I believe always, but not 100% sure) a push animation instead of pop, which might not be the desired one.
So if you want to make the navigation controller to perform a pop animation, then you should call pop until you reach the desired view controller, but the key is to animated a single pop. For the same example as above:
guard let navigationController = self.navigationController else { return }
let numberOfPops = navigationController.viewControllers.count - 3
for i in 0..<numberOfPops {
let animated = i == 0
navigationController.popViewController(animated: animated)
}
Hope this answers the question about popping to the desired view controller (you can detect your view controller by its type, then you can find out its index and use the logic above).
If you want to dismiss to the first presenting view controller, then you need to call UIViewController.dismiss(animated:completion:) on the first presenting view controller. dismiss method will behaves like this:
If the view controller is presenting a view controller, then it will dismiss the presented view controller;
If the view controller is not presenting any view controller, but it is presented by another view controller (i.e. has a parent view controller), then it will ask the parent view controller to dismiss it;
If none of the above, it will do nothing.
So long story short, you need to call dismiss on the view controller that you want to be left on the screen.
I basically need it to act as if the app was just relaunched.
In this case, assigning the login/register screen controller to UIWindow.rootViewController seems to be the right option. This topic has been already covered here: Swift ios set a new root view controller
I have a basic scenario:
I present a VC modally using self.present(, animated:, completion:).
Sometimes due to interactions in this modal VC i need to close one modal and open another one.
So i do the following:
weak var presenter = self.presentingViewController
let newVc = UIViewController()
presenter?.dismiss(animated: true, completion: {
presenter?.present(newVc, animated: true, completion: nil)
})
This works but there is the annoying delay when switching the VC's when user sees the original presenter and can try to interact with it (to open other modals...).
I tried setting animated: false but that doesn't seem to work :/
I can't really switch to UINavigationController model for this because the modals i am presenting themselves are Page View Controllers and have the whole hierarchy of dependent views; the user is never going 'back'; so i'd really like to just present the new modal as quickly as possible...
Update My question is not about how to control or choose the animation. My questions is about having no delay between the modals.
The built-in view controller architecture that switches views with no transition is the tab bar controller. So just turn your view controller into a tab bar controller — with no visible tab bar! To change to the other view controller just change tabs (in code). The change is instant.
This screencast makes it clear that this works as described. We present a view controller (yellow). Then we switch back and forth between two view controllers (green and yellow) as the presented view controllers, instantly. Finally, we dismiss whichever one (green or yellow) is showing. I'm doing it all with simple buttons but that's just for the demo; obviously you could do this however you like. It's the architecture that's the important thing.
I can think only of solutions which would require you to handle the animations yourself
create custom modal transition using UIViewControllerTransitionCoordinator
add your controllers to container views as suggested by #muhammed-gül
present newVC over self and dismiss all presented controllers when you're done
And just a tip, you don't always need to wait for the dismiss completion closure, you can call this and it usually works, but still the underlying viewController is visible.
dismiss(animated: true)
present(newVC, animated: true)
I've had this issue for months with multiple views, both Apple provided like ImagePicker and VCs from storyboard.
I believe that it has something to do with the underlying views we have both a tab bar controller and navigation controller in most views.
Strange thing is using some open source views from pods does not cause this bug.
So I'm two views deep on a navigation controller and present another view modally on top with present(vc, animated: true, completion: {})
Works like a charm, now dismissing that view with dismiss(animated: true, completion: nil) throws me back all the way to the initial view or root view of the navigation controller, had both happen before, depending on the presented view.
Update:
Build a sample project trying to reproduce the behavior but failed. Drew a reduced diagram to better explain the current bug behavior.
Also noticed that if I'm invoking the post view one step earlier in the Fandom view it works as expected.
In my case i am using UITabBarController, and I wrote code in viewWillAppear of UITabBarController
self.selectedIndex = 2
so when i present any thing from any controller whose parent is UITabBarController and when i dismiss that it automatically open third tab of UITabBarController.
Maybe you explicitly wrote any code to select specific index of TabBar.
Maybe this is useful for you or anyone else.
current i am developing this app and i have the main and login screen.
The architecture works in such a way that when the app launches, the main screen will always present first.
then, if the user is not logged in, the login screen will be presented modally on top of the main screen. (i think this is a normal approach right?).
However, the thing is now that, whenever i do the following code.
self.present(mainNavigationController, animated: false, completion: {
let storyboard = UIStoryboard(name: "LoginSignUp", bundle: nil)
let loginNavigationController = storyboard.instantiateViewController(withIdentifier: "loginNavigationViewController") as! UINavigationController
mainNavigationController.present(loginNavigationController, animated: false, completion: nil)
})
I will always see a blank white main screen before the the login screen is shown, that is kinda ugly.. Anyone has any solution such that these two view controllers are stacked on top of each other from the very beginning and shown once, rather than shown in sequence?
You code is trying to accomplish this:
Present main navigation controller modally over the current view controllers navigation.
Then after showing it present login modally on mainNavigation controller.
You need to change in this way:
Push your main screen in current navigation stack by pushViewController.
And then try to present login screen modally over the current navigation stack.