AlertController trying to be presented on view not in window hierarchy - swift

Trying to figure out why I am getting this error. I have a single view application with two viewcontrollers. I have a segue to the second vc on a button touch:
#IBAction func showAccount(_ sender: Any) {
performSegue(withIdentifier: "toAccountFromRoot", sender: self)
}
I have a facade pattern set up with a Controller file and several helper files. I set the Controller up on the AccountViewController file:
let myController = Controller.sharedInstance
One of the helper files is to set up a central AlertController for simplified alerts (no extra buttons). It has one method, presentAlert that takes a few arguments, alert title, messsage, presenter.
On my AccountViewController I am attempting to present an alert using this code:
myController.alert.presentAlert("No Agent", "You have not registered an agent yet.", self)
and in the AlertManager file, presentAlert looks like this:
func presentAlert(_ title:String, _ message:String, _ presenter:UIViewController) {
let myAlert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
myAlert.addAction(okAction)
presenter.present(myAlert, animated:true, completion:nil)
}
I've done this in quite a few apps without an issue so I am confused as to why the compiler thinks the AccountViewController view is not in the window hierarchy.

Dang it, typed all that and then I solved it 5 seconds later. Just in case someone else runs into the same problem, I was trying to present the alert in viewDidLoad, should of been doing it in viewDidAppear.

Related

Can't move from a VC to another one, can't figure out why

I have a register screen in my app, after the user writes done a phone number and agrees to the terms, and clicking "register" the app should move the user to the next vc. The problem is, although everything looks to be ok, the app won't move to the next screen.
This is my code:
func handleRegister() {
if UserDefaults.standard.object(forKey: USER_AGREED) == nil {
let alert = UIAlertController(title: "Terms and Conditions", message: "Please agree to the terms and conditions to continue", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (alert) in
self.showTermsAndConditions()
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .destructive, handler: nil))
self.present(alert, animated: true)
}else{
guard let userPhoneNumber = self.mView.textField.text else { return }
UserDefaults.standard.set(userPhoneNumber, forKey: USER_PHONE_NUMBER)
let vc = Tabbar()
self.navigationController?.pushViewController(vc, animated: true)
}
}
I tried to put a breakpoint, and it does gets to the else block when it should, and recognizes the Tabbar, but just won't move there.
Any suggestions?
There are two possible sources of trouble here.
You are saying self.navigationController?. The question mark means: "If I have no navigationController, do nothing." If you have no navigationController, you do nothing. Perhaps what you meant to say is self.present(vc, animated:true).
You are saying let vc = Tabbar(). That is not an existing view controller in the interface or storyboard. So when you move there, it might be empty (that depends on how Tabbar view controller is constructed).

ViewController segue and login

I'm new to Swift (and programming in general). I'm having some difficulties in Xcode with the login feature. What I want is that the user should be logged in if no errors was returned and the user should be sent to another controller. I've read some of the documentation from Apple and the performSegueWithIdentifier (and of course some questions asked here), but it still does not work when I use a segue with push or modal that are given an identifier. Either the app crashes, or the user is sent to my UITabBarController even if the credentials was incorrect.
This is my LogInViewController.swift:
#IBAction func loginAction(sender: AnyObject)
{
if self.emailField.text == "" || self.passwordField.text == ""
{
let alertController = UIAlertController(title: "Oops!", message: "Please enter an email and password.", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alertController.addAction(defaultAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
else
{
FIRAuth.auth()?.signInWithEmail(self.emailField.text!, password: self.passwordField.text!) { (user, error) in
if error == nil
{
self.emailField.text = ""
self.passwordField.text = ""
}
else
{
let alertController = UIAlertController(title: "Oops!", message: error?.localizedDescription, preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alertController.addAction(defaultAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
// User Logged in
self.performSegueWithIdentifier("LoggedIn", sender: self)
}
}
}
The error I get in the console:
2016-09-04 14:55:30.019 DrinkApp[37777:1006336] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Could not find a navigation controller for segue 'LoggedIn'. Push segues can only be used when the source controller is managed by an instance of UINavigationController.'
There are two main ways to send a user to another View Controller. The first is performSegueWithIdentifier. Here's how you would go about using that:
First of all, control-drag from the yellow circle of the original VC to the target VC in the interface builder.
Next, click on the 'segue arrow' that appears between the two View Controllers.
In the identifier field, type in mySegue.
Next, go back to your code. When you reach the sign in section, add the following code:
self.performSegueWithIdentifier("mySegue", sender: nil)
The second method is a bit more advanced. Here's how that would work:
First, click on the yellow circle of the destination VC. Click on the newspaper, then call the storyboard ID myVC.
Make sure to click on Storyboard ID
Go back to the same part of the code and copy and paste this code:
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initViewController: UIViewController = storyboard.instantiateViewControllerWithIdentifier("myVC") as UIViewController
self.presentViewController(initViewController, animated: true, completion: nil)
Comment down below if you have any questions. Good luck!
Here is a Firebase login example:
func login() {
FIRAuth.auth()?.signInWithEmail(textFieldLoginEmail.text!, password: textFieldLoginPassword.text!) { (user, error) in
if error != nil {
let alert = UIAlertController(title: "User Authentication error",
message: error?.localizedDescription,
preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK",
style: .Default) { (action: UIAlertAction!) -> Void in
}
let resetPasswordAction = UIAlertAction(title: "OK",
style: .Default) { (action: UIAlertAction!) -> Void in
}
alert.addAction(OKAction)
guard let errorCode = FIRAuthErrorCode(rawValue: (error?.code)!) else { return }
switch errorCode {
case .ErrorCodeWrongPassword:
alert.addAction(resetPasswordAction)
case .ErrorCodeNetworkError:
print("Network error")
default:
print("Other error\n")
break
}
self.presentViewController(alert, animated: true, completion: nil)
} else { // User Logged in
self.performSegueWithIdentifier("MySegue", sender: self)
}
}
}
I only handled two errors for this example.
If you use push segue, you need to have navigation controller, and your root segue should lead to the login view controller, then another segue to the next view controller. This last segue must have an identifier (in this case it's MySegue)
Edit:
Since it seems that your problem is not with Firebase, but with setting up the storyboard. Here is an example:
Set up one UINavigationController two UIViewControllers
Control Drag between the UINavigationController to the middle UIViewControllers and choose root view controller from the popup menu
Control Drag between the UIViewController to the last UIViewControllers and choose show
select the segue added in the previous step and change its identifier (in the attributes inspector) to MySegue.
5.Choose the Middle UIViewControllers and change the class name (in the identity inspector) to your login view controller class.
Here is the full setup:
control drag menu (step 2):
Segue attributes inspector (step 4):
I found this answer mentioned here to be super elegant.
https://fluffy.es/how-to-transition-from-login-screen-to-tab-bar-controller/
it works with tab bars, and nav bars. you create a function in the scene delegate, that switches the root view controller. you access the scene delgates functions and windows with (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?

How to get back to viewcontroller in alertview

I tried to get back to the viewcontroller when user press Go home in the alertView and it show me the error ( libc++abi.dylib: terminating with uncaught exception of type NSException )
#IBAction func roundButton(sender: AnyObject) {
//add alert
alertController = UIAlertController(title: "Return Home", message: "Are you sure???", preferredStyle: .Alert)
let alertAction1 = UIAlertAction(title: "Cancel", style: .Default) { (action) in
}
let alertAction2 = UIAlertAction(title: "Go Home", style: .Destructive) { (action) in
let nv = self.storyboard!.instantiateViewControllerWithIdentifier("storyboardidentifier") as! ViewController
self.presentViewController(nv, animated:true, completion:nil)
}
alertController?.addAction(alertAction2)
alertController?.addAction(alertAction1)
self.presentViewController(alertController!, animated: true, completion: nil)
//end alert
}
Since you didn't provide much information, there are a lot of possibilities. I will try to cover most of them.
Let's say that you are presenting the alert in BViewController. And you want to get back to AViewController.
If AViewController and BViewController are embedded in a UINavgationController, you will have to connect an unwind segue between BViewController and AViewController.
First, in AViewController, add this method:
#IBAction func unwind(segue: UIStoryBoardSegue) {
}
In the storyboard, control-drag from BViewController to the "Exit" of `AviewController and select "unwind:" aka the method you just declared. This is what you should see
Now give the segue you just added an identifier, say "unwindToHome".
In the UIAlertAction call back closure, initiate the segue:
self.performSegueWithIdentifier("unwindToHome", sender: self)
And that's it!
If you are presenting the BViewController modally, then things are simpler, just call dismissViewControllerAnimated:
self.dismissViewControllerAnimated(true, completion: nil)
Create a segue on your storyboard between the two view controllers and give it an Identifier, then, on the completion handler of your alert action, you can trigger the segue. Something along the lines of:
let alertAction2 = UIAlertAction(title: "Go Home", style: .Destructive) { (action) in
self.performSegueWithIdentifier("MyStoryboardSegue", sender: self)
}
Good luck!

How to show one UIAlertController after the other in Swift

I want to show user a message once.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let defaultsDatafirstTrue = NSUserDefaults.standardUserDefaults()
if let _ = defaultsDatafirstTrue.stringForKey("firstTrue") {
} else {
let alertController = UIAlertController(title: "You can add road markers just do long press on the Map", message: "", preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action:UIAlertAction) in
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true, completion:nil)
defaultsDatafirstTrue.setObject("true", forKey: "firstTrue")
}
}
But I have error Warning: Attempt to present <UIAlertController: 0x7c2a8000> on <****: 0x7d193800> whose view is not in the window hierarchy! Because at the first run the iOS app displays a warning to the user that will use the location determination.
How can I see my message after the system message?
Run the code from viewDidAppear: not from viewWillAppear:.
The problem with viewWillAppear: is that it is called before the view is actually visible, so you cannot present another view from it yet. Also, viewWillAppear: can be actually called multiple times and then cancelled (e.g. for interactive transitions).

UIAlertController button click navigates to a NavigationController's first ViewController

I am new to swift and iOS programming. I have StartViewController which has a button which when clicked a UIAlertController with two buttons - Decline & Accept. When clicking on Accept, I want to navigate to a MyNavigationController's - ViewController. The MyNavigationController has four ViewControllers which I navigate to using a slide menu.
I am attaching a sample code and screenshot of my storyboard for reference.
#IBAction func showAlert() {
let alertController = UIAlertController(title: "Disclaimer", message: "Before using this teaching resource, you confirm that you agree:\n1. To obey the law regarding data protection and patient confidentiality.\n2. To us this app professionally and appropriately in clinical settings.\n3. This is for your personal use and you may not modify, distribute, publish, transfer any information obtained from this teaching resource without the developers' permission.\n4. In no event shall the developer be liable to you for any loss arising from your use of this resource.", preferredStyle: .Alert)
let declineAction = UIAlertAction(title: "Decline", style: .Cancel, handler: nil)
alertController.addAction(declineAction)
let acceptAction = UIAlertAction(title: "Accept", style: .Default) { (_) -> Void in
//I think the code to navigate should go here, help please.
}
alertController.addAction(acceptAction)
presentViewController(alertController, animated: true, completion: nil)
}
Storyboard Screenshot -
In the screenshot above, the Login button opens up a UIAlertController alertController. Accept button on this AlertController should navigate to ViewController on the MyNavigationController.
This MyNavigationController has three other ViewControllers which are navigated using a slide menu.
Thank you in advance.
You need to create a segue between your current view controller and the destination view controller and make sure the segue ID matches the id in your performSegueWithIdentifier call
#IBAction func showAlert() {
let alertController = UIAlertController(title: "Disclaimer", message: "Before using this teaching resource, you confirm that you agree:\n1. To obey the law regarding data protection and patient confidentiality.\n2. To us this app professionally and appropriately in clinical settings.\n3. This is for your personal use and you may not modify, distribute, publish, transfer any information obtained from this teaching resource without the developers' permission.\n4. In no event shall the developer be liable to you for any loss arising from your use of this resource.", preferredStyle: .Alert)
let declineAction = UIAlertAction(title: "Decline", style: .Cancel, handler: nil)
let acceptAction = UIAlertAction(title: "Accept", style: .Default) { (_) -> Void in
self.performSegueWithIdentifier("SomeSegue", sender: self) // Replace SomeSegue with your segue identifier
}
alertController.addAction(declineAction)
alertController.addAction(acceptAction)
presentViewController(alertController, animated: true, completion: nil)
}