Issue loading View Controller from UITabBarController - ios

In the past my app has had only 1 main view controller (MainViewController) and a login view controller (LoginViewController) but now I am moving to a Tab Bar Controller.
Before I was able to do a simple check viewDidLoad of MainViewController for the existence of a username and password in the key chain. If a username and password was not present I used a segue to pop up a modal login view controller.
With the new setup of using a Tab Bar Controller I still only have 1 view controller (MainViewController) which is the root view controller (as of now) and I am trying to do the same thing where it pops up modal of the login screen.
Now when I call the segue in the viewDidLoad of MainViewController:
[self performSegueWithIdentifier:#"loadLoginView" sender:nil];
I am getting this error:
Warning: Attempt to present <LoginViewController: 0x1757cd80> on <UITabBarController: 0x17571e50> whose view is not in the window hierarchy!
But if I associate a button to a method that loads the LoginViewController by way of a segue it works fine. I am doing that in the MainViewController like this:
-(void)loadLogin
{
[self performSegueWithIdentifier:#"loadLoginView" sender:nil];
}
I can see from the error message that when I try to perform the segue from the viewDidLoad of MainViewController it's trying to load the LoginViewController from UITabBarController.
Why can I not load the LoginViewController from the viewDidLoad of MainViewController?
Any help with this would be great.
Thanks!

It looks like -viewDidLoad is getting called before your view controller stack is added to the window. Two things you could try are:
Delaying until the next time through the run loop (this should give the view controllers time to get in place) [self performSelector:#selector(loadLogin) withObject:self afterDelay:0];. This method won't allow you to call a method with two arguments directly
You could use -presentViewController: animated: completion:. This will cause your login controller to slide in from the bottom.

Related

Change view in code swift

i have this Swift project where i have a tab bar Controller Scene, and i would like to add a view controller scene to act as a "Login scene", when the user is loged i want to switch the view to the tab bar controller scene.
I have implemented facebook login, so i have a method that is called when the login is completed.
I have done this:
func loginDone(){
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("dashboardScreen") as! UITabBarController
self.presentViewController(vc, animated: true, completion: nil)
}
But that code brings me this error:
Attempt to present <UITabBarController: 0x7f87a37e8550> on <Project.LoginViewController: 0x7f87a352fca0> whose view is not in the window hierarchy!
What should i do?
Thanks!
Call it in viewDidAppear(), that way you can be sure that the login view is part of the app's view hierarchy.
You need to make sure the presenting view controller is in the app's view hierarchy. See the section 'Responding to View Events' in the UIViewController Class Reference
If you need more information on working with view controller's you can look at the View Controller Programming Guide
You should not call loginDone from init, viewDidLoad methods and others that are called before view is shown. Call it from viewDidAppear.

presenting a modal view controller without storyboard

I have created a custom UIWebView to present a login page. I am able to get this to show by adding it as a subview. But I would like to present this modally and programatically without the use of a storyboard!
Put it in an otherwise empty view controller, and then call
[self presentViewController: animated: completion:]
in the view controller you're wanting to present it from. When you're done, you can call
[self dismissViewControllerAnimated: completion:]
to get rid of it again. This message can be sent to either the presenter or the presented view controller, though in either case it ultimately is the presenter which responds.

Link to another ViewController inside viewDidLoad method

in the viewDidLoad method i have this code.
When the application is running its not go to the another viewController, its gives me an error:
Warning: Attempt to present <CompleteCountryViewController: 0x7fb971779be0> on <ViewController: 0x7fb97176f3e0> whose view is not in the window hierarchy!
What can i do, that when the application running its will go to another viewController?
You should not present a view controller in the viewDidLoad method of another controller because you cannot show a view controller (present modally or push) when a transition is already occurring (push, pop, present, dismiss).
My suggestion is that you move the code in your code sample to the viewDidAppear: method. At this point, you know for sure that the transition has completed.
You seem to have a slight misunderstanding of the lifecycle of UIViewController if you want to modally present a view controller inside the viewDidLoad of another one.
viewDidLoad gets called in one view controller after it has been instantiated and its view components have been loaded (thus the name). The view of that view controller is about to be displayed, so it doesn't make much sense to instantiate another view controller at this point and present it on the first one.
Let me give you an example with two view controller A and B.
You instantiate A and its viewDidLoad gets called. So, A is about to be displayed! What you are doing in your code now is to instantiate B at this very point and show it on A. iOS doesn't like that and will give you your error.
I had an issue where I was attempting to present a modal view controller within the viewDidLoad method. The solution for me was to move this call to the viewDidAppear: method.
View controller's view is not in the window's view hierarchy at the point that it has been loaded (when the viewDidLoad message is sent), but it is in the window hierarchy after it has been presented (when the viewDidAppear: message is sent).

Creating a central log out view controller in storyboard

I am working with Parse, and one thing I have implemented in my app is their built in PFLogInViewController. This controller will be presented at two times in the application - when the app first starts and the user is not logged in, and when the user taps the "Log out" button of my application (logging out takes them back to the PFLogInViewController, as you are required to sign in to use the app). I would like to set this up using Storyboard, as that is how the rest of my app is laid out. How could I set up a central view controller (a PFLogInViewController) that is accessed at these two times? I have already Subclassed PFLogInViewController and set it up, I just need advice on how to place it in Storyboard and how to connect it to my views. To make this question help as many people as possible, the general theme of my question is how does one establish a central Login/ViewController that can be accessed at different points in the application using Storyboard. Attached is the basic idea of what I'm trying to accomplish. I haven't been able to successfully segue to the initial TabBarController, and I'm not sure how I should make the LoginController the initial ViewController if I can't segue. I am programming in Swift, if it matters.
There are a few ways to do this depending upon your application. One way is drop a UIViewController onto the storyboard, but don't wire it up to anything (no segue). Create a storyboard id for it such as "MyLoginVC". Do the necessary subclassing of UIViewController and attach the class to your VC. Then, when you want to display the VC simply do the following or wire this up to your logout button
id destinationVC = [self.storyboard instantiateViewControllerWithIdentifier:#"MyLoginVC"];
[self.navigationController pushViewController:destinationVC animated:YES];
In addition, if you want to show the login VC as the very first VC when you launch your app, then perhaps in your AppDelegate
// Load Root view controller
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
self.rootVC = [storyboard instantiateInitialViewController];
self.window.rootViewController = _rootVC;
[self.window makeKeyAndVisible];
// Load Login view controller
id initialVC = [storyboard instantiateViewControllerWithIdentifier:#"MyLoginVC"];
[initialVC setModalPresentationStyle:UIModalPresentationFullScreen];
[_rootVC presentModalViewController:initialVC animated:NO];
When you finish with your login VC (i.e. successful login) then within login VC
[self dismissViewControllerAnimated:NO completion:nil];
and alternatively instantiate your first VC with something similar to the following from within login VC. Note, since you loaded the root VC above first, it is already there with the login VC sitting over it. When you dismiss login VC, the underlying root VC should be ready to rock and roll. Otherwise you can do the following:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
RootTabBarController *tbController = (RootTabBarController *)[self.storyboard instantiateViewControllerWithIdentifier:#"rootTabBarController"];
[self presentViewController:tbController animated:YES completion:NULL];
}
I think what you want is an unwind segue. Here are the instructions I follow for an unwind segue: https://github.com/bradley/iOSUnwindSegueProgramatically
If the link dies, here is what it said:
In your storyboard create two view controllers.
Subclass UIViewController twice, once for each of the view controllers in your storyboard.
Connect these view controllers to the view controllers in your storyboard.
Make a segue between the first view controller and the second by control+dragging from the first to the second.
Click on the segue you created and give it an identifier in the attributes inspector.
Make a button on the first view controller and link it to an IBAction in its UIViewController subclass.
When this button is pressed, the second storyboard should appear. To make this happen (we are doing it programatically) put the following into the implementation of the action you just created:
[self performSegueWithIdentifier:#"nameOfTheSegueBetweenOneAndTwo" sender:self];
Create a second method in the implemention of the first view controller with the following:
- (IBAction)returnToStepOne:(UIStoryboardSegue *)segue {
NSLog(#"And now we are back.");
}
This method will work to unwind any view controller back to this view controller. Notice that we implement the method in the view controller we wish to return to.
Go back to the storyboard. Focus in on the second view controller. If it is active, you should see a dark bar beneath it with 3 symbols on it. One of these is orange and when hovered over will show the name of the UIViewController subclass that this view controller represents. Control drag from this symbol woth the green symbol that means 'Exit'. You should see all available segue unwinds, which XCode automatically enumerates when you create segue unwind implementations inside UIViewController subclasses that you have shown on your stroryboard. Hence, you should see the segue 'returnToStepOne' as an option. Select it.
In your storyboard's document outline, find the section for the second view controller. You should see an item listed below it with a grey symbol that says something like "Unwind segue from ... to Exit." Click on this item.
Important and easily missed step follows!
On the right side of your storyboard, in the attributes inspector, you should see two fields. One for 'Identifier' and one for 'Action'. In most cases, the 'Action' field will have the text 'returnToStepOne:', which is what we want, but the 'Identifier' field will be blank. Fill this field with the text: 'returnToStepOne' (note that we leave out the colon).
Create a button on the second view controller and link it to an IBAction in its UIViewController subclass.
In the implementation for the method you just created, put the following code:
[self performSegueWithIdentifier:#"returnToStepOne" sender:self];
Run the application. You should now be able to unwind from the second view controller to the first.

remove all uiviewcontroller from hierarchy to display the first uiviewcontroller

When my App is starting I have a modal view controller to enter credentials (IP#, username, password...). When the user is logged he can open many UIViewControllers that can open others UIViewController and so on... These view controller can be navigation controller, tab bars, modals....
Next, the user can leave the application in background. Next, user can open the Mail App and open a mail that contains an attachment to navigate to my App. When the user select my App to open the attachment.
When the App moves in foreground I need to go to a specific viewController of the hierarchy (the first ViewController opened after the login screen).
To move the App to this first ViewController I have tried to use
– dismissViewControllerAnimated:completion:
but without success, there're still a view from the hierarchy that is still displayed.
Any idea how to do this?
Regards,
Sebastien.
If all the view controllers are in 1 Navigation controller and pushed onto the stack, you can simply use either popToRootViewControllerAnimated: or popToViewController:animated:
Otherwise, why not dismiss the first Modal view controller, then put it back with the view controller you want.
I found a solution with this small piece of code
UINavigationController* navc = (UINavigationController*)viewController.topViewController;
if (navc != Nil) {
if (navc.presentedViewController != Nil) {
[navc dismissViewControllerAnimated:FALSE completion:Nil];
}
[navc popToRootViewControllerAnimated:TRUE];
}
It was not complex at the end!
Sébastien.

Resources