Idan Aviv Idan Aviv - 1 month ago 17
Swift Question

Presenting An Error from App delegate -- Google Signin

I am using Firebase in my app and I want to add google signin functions. According to Google, I should add the sign function to my Appdelegate. My Problem is that if I want to perform the login from the appdelegate how could I present Error to the user. I tried the following code.

public func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!){

if let error = error {
print()
let importantAlert: UIAlertController = UIAlertController(title: "Error", message: "\(error.localizedDescription)", preferredStyle: .alert)
self.window?.rootViewController?.present(importantAlert, animated: true, completion: nil)

return
}
let authentication = user.authentication
let credential = FIRGoogleAuthProvider.credential(withIDToken: (authentication?.idToken)!,
accessToken: (authentication?.accessToken)!)
FIRAuth.auth()?.signIn(with: credential, completion: { [weak self](user, error1) in
if(error != nil){

let importantAlert: UIAlertController = UIAlertController(title: "Error", message: "\(error1!.localizedDescription)", preferredStyle: .alert)
self?.window?.rootViewController?.present(importantAlert, animated: true, completion: nil)
}
})
}


And I get the following error

Warning: Attempt to present <UIAlertController: 0x7f864b62bef0> on <SFSafariViewController: 0x7f864b40edb0> whose view is not in the window hierarchy!


what should I do ?

Answer

The error says, your self.window?.rootViewController is of type SFSafariViewController and its .view is not in the window hierarchy at the moment you are calling sign().

If you do not set it programatically, you probably set it in Storyboard (the ViewController with the big arrow on the left side (->).

Probably you are showing another UIViewController at the moment you call sign().

Two options:

1) Move the sign() method to the UIViewController who is calling it

Assuming you have a UIViewController for login, called MyLoginViewController, move your sign() method from the AppDelegate to MyLoginViewController and replace self?.window?.rootViewController? with self:

public func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!){

    if let error = error {
        print()
        let importantAlert: UIAlertController = UIAlertController(title: "Error", message: "\(error.localizedDescription)", preferredStyle: .alert)
        self.present(importantAlert, animated: true, completion: nil)

        return
    }
    let authentication = user.authentication
    let credential = FIRGoogleAuthProvider.credential(withIDToken: (authentication?.idToken)!,
                                                  accessToken: (authentication?.accessToken)!)
    FIRAuth.auth()?.signIn(with: credential, completion: { [weak self](user, error1) in
    if(error != nil){

        let importantAlert: UIAlertController =     UIAlertController(title: "Error", message: "\(error1!.localizedDescription)", preferredStyle: .alert)
            self.present(importantAlert, animated: true, completion: nil)
        }
    })
}

2) Pass ViewController as argument

If you want to keep the sign() method at a global place, i.e. in AppDelegate, add an argument to your sign() method to pass the calling UIViewCOntroller:

In AppDelegate:

public func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, presenting viewController: UIVIewController, withError error: Error!){

    if let error = error {
        print()
        let importantAlert: UIAlertController = UIAlertController(title: "Error", message: "\(error.localizedDescription)", preferredStyle: .alert)
        viewController.present(importantAlert, animated: true, completion: nil)

        return
    }
    let authentication = user.authentication
    let credential = FIRGoogleAuthProvider.credential(withIDToken: (authentication?.idToken)!,
                                                  accessToken: (authentication?.accessToken)!)
    FIRAuth.auth()?.signIn(with: credential, completion: { [weak self](user, error1) in
    if(error != nil){

        let importantAlert: UIAlertController =     UIAlertController(title: "Error", message: "\(error1!.localizedDescription)", preferredStyle: .alert)
            viewController.present(importantAlert, animated: true, completion: nil)
        }
    })
}

and call it like this in the currently presented UIViewController:

sign(yourGIDSignIn, didSignInFor: yourGIDGoogleUser, presenting self, withError: yourError)