Sign In with Google Authentication - swift

I've implemented Google Authentication via Firebase in my App. Everything works smoothly except for one small problem that I can't seem to find. Whenever the user opens the page that prompts them to "Sign in with Google" (ie. login page or sign up page), the banner appears momentarily before disappearing. I do not want it to appear at all, unless the user clicks the "Sign in with Google" button. How can I get rid of this?
WelcomeViewController (the view controller with the google login)
import UIKit
import FirebaseAuth
import Firebase
import FBSDKLoginKit
import GoogleSignIn
class WelcomeViewController: UIViewController, GIDSignInDelegate {
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var signInFacebookButton: UIButton!
#IBOutlet weak var signInGoogleButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setUpGoogleButton()
GIDSignIn.sharedInstance()?.presentingViewController = self
GIDSignIn.sharedInstance().signIn()
}
// SIGN IN WITH GOOGLE
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let err = error {
print("Failed to log into Google: ", err)
return
}
print("Successfully logged into Google")
guard let authentication = user.authentication else { return }
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential, completion: { (user, error) in
if let err = error {
print("Failed to create a Firebase User with Google account: ", err)
return
}
// Successfully logged in
guard let uid = user?.user.uid else { return }
print("Successfully logged into Firebase with Google", uid)
// switch to tab bar controller
let tabBarC = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController") as! TabBarController
tabBarC.modalPresentationStyle = .fullScreen
self.present(tabBarC, animated: true, completion: nil)
print("Switched to TabBarController")
})
}
fileprivate func setUpGoogleButton() {
let button = signInGoogleButton
button?.layer.borderWidth = 0
button?.backgroundColor = UIColor.init(red: 130/255, green: 178/255, blue: 189/255, alpha: 1)
button?.layer.cornerRadius = 20.0
button?.tintColor = UIColor.white
button!.addTarget(self, action:
#selector(handleCustomGoogleSignIn), for: .touchUpInside)
GIDSignIn.sharedInstance()?.delegate = self
}
#objc func handleCustomGoogleSignIn() {
GIDSignIn.sharedInstance().signIn()
}
I've attached a link to a screen recording of what happens. The second page shown in the screen recording is identical to the code below, so it has the same problem. Any help is appreciated, thank you!
https://drive.google.com/file/d/1t4KV0Z6qwfCK56Gf2314wXWhAeQR0wUs/view?usp=sharing

That's because of your code inside viewDidLoad(). You are implementing this method:
GIDSignIn.sharedInstance().signIn()
This triggers the sign in method as soon as the view loads (as you are implementing it inside viewDidLoad()), and that causes that momentary sign in pop up that disappears.
Instead of implementing that method there, you should only implement it inside your handleCustomGoogleSignIn().
Conclusion, your viewDidLoad() should look like this:
override func viewDidLoad() {
super.viewDidLoad()
setUpGoogleButton()
GIDSignIn.sharedInstance()?.presentingViewController = self
}

Related

facebook login jump to another page

I want to jump to Dashboard after I login with FB SDK. and I can get logged in and already logged. but I'm not able to jump to Dashboard. what's the problem?
Thank you very much
class ViewController: UIViewController, FBSDKLoginButtonDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// login button
let loginButton = FBSDKLoginButton()
view.addSubview(loginButton)
loginButton.frame = CGRect(x: 16, y: 500, width: view.frame.width - 40, height: 50)
// getting login status back
loginButton.delegate = self
// if login then head to dashboard
if FBSDKAccessToken.current() == nil {
// User is not already logged
print("No Logged")
} else {
// User is already logged
fetchProfile()
print("Already Logged")
}
}
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
if error != nil {
print(error)
return
}
// jump to Dashboard
performSegue(withIdentifier: "goToDashboard", sender: self)
print("logged in")
}
func loginButtonDidLogOut(_ loginButton: FBSDKLoginButton!) {
print("logged out")
}
}
performSeague doesn't work here.
this is another method I'm using
func jumpToDashboard() {
let next = storyboard?.instantiateViewController(withIdentifier: "Dashboard")
self.present(next!, animated: true, completion: nil)
}

How can I get the Google Sign in Button to work in Xcode 9.3?

I am having trouble getting my google sign in button working in Xcode 9.3-beta.
Here is the code from my app delegate:
import UIKit
import Firebase
import GoogleSignIn
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//Initialize Firebase
FirebaseApp.configure()
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
GIDSignIn.sharedInstance().delegate = self
// Override point for customization after application launch.
return true
}
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error?) {
// ...
if error != nil {
// ...
return
}
guard let authentication = user.authentication else { return }
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
// ...
}
#available(iOS 9.3, *)
func application(_ application: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any])
-> Bool {
return GIDSignIn.sharedInstance().handle(url, sourceApplication:options[UIApplicationOpenURLOptionsKey.sourceApplication] as? String, annotation: [:])
}
}
And here is the code for my register View Controller:
import UIKit
import Firebase
import GoogleSignIn
class registerViewController: UIViewController, GIDSignInUIDelegate {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var signInButton: GIDSignInButton!
//Email_password combination
#IBAction func complete(_ sender: Any) {
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordTextField.text!) {
(user, error) in
if error != nil {
print(error!)
} else {
//success
let welcomeMessage = "Signed In"
let alert = UIAlertController(title: welcomeMessage, message: "Welcome", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"), style: .`default`, handler: { _ in
NSLog("The \"OK\" alert occured.")
}))
self.present(alert, animated: true, completion: nil)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().signIn()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//GID sign in method
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error?) {
// ...
if error != nil {
// ...
return
}
guard let authentication = user.authentication else { return }
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential) { (user, error) in
if error != nil {
// ...
return
}
// User is signed in
// ...
}
// ...
}
When I run this app, I can navigate successfully to my register view controller and my view for the GID sign in button loads successfully. however, nothing happens when I click the button. I've been working with this for hours and haven't gotten anywhere.
In the debug console, no errors come up. but I do get at the top:
"Unknown class _TtC4LEAF14ViewController in Interface Builder file."
I also get
"Status bar could not find cached time string image. Rendering in-process."
I can't figure out what either of these are referring to. I made sure to use the correct URL types, and have incorporated the pod files correctly. My email-password combination works as well.
I had similar problem with Xcode 9.3 (official version) and Google login. I tested with Xcode 9.4 beta and everything works again.

Swift: How to check if user's phone number is already in Firebase database before creating a new user

I just started working with Swift a few months ago and to help me learn the language better, I am creating a chat application. For the sign-in method, I am using the phone number method. I have the onboarding process already created but I want to implement something that lets me check if the user has already created an account with that phone number. If they have, I want to segue them to the main view controller, skipping the onboarding view controller.
Here is my code for the phone verification view controllers (One is for inputting a phone number and the other is for inputting the code sent to the user's phone):
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
class PhoneVerification: UIViewController {
//MARK: Properties
#IBOutlet weak var phoneNumber: UITextField!
#IBOutlet weak var code: UITextField!
#IBOutlet weak var verifyCodeImage: UIButton!
#IBOutlet weak var sendCodeImage: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//MARK: Format phone text field
var phoneFormatter = PhoneNumberFormatter()
#IBAction func formatPhoneNumber(_ sender: UITextField) {
sender.text = phoneFormatter.format(sender.text!, hash: sender.hash)
}
//MARK: When send secret code button is pressed
#IBAction func sendCode(_ sender: Any) {
let submitPhoneNumber = "+1" + phoneNumber.text!
if submitPhoneNumber.count > 9{
PhoneAuthProvider.provider().verifyPhoneNumber(submitPhoneNumber, uiDelegate: nil) {(verificationID, error) in
if error != nil {
print(error!)
}else{
UserDefaults.standard.set(verificationID, forKey: "authVerificationID")
self.performSegue(withIdentifier: "phoneCode", sender: self)
}
}
}else{
let phoneNumAlert = UIAlertController(title: "Please enter your phone number", message: "You must enter your phone number to continue.", preferredStyle: .alert)
phoneNumAlert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil))
self.present(phoneNumAlert, animated: true)
}
}
let verificationID = UserDefaults.standard.string(forKey: "authVerificationID")
//MARK: When verify code button is pressed
#IBAction func verifyCode(_ sender: Any) {
let credential = PhoneAuthProvider.provider().credential(
withVerificationID: verificationID!,
verificationCode: code.text!)
//This is where the user is signed in if the verification code is correct
Auth.auth().signIn(with: credential) { (user, error) in
if let error = error {
let invalidCodeAlert = UIAlertController(title: "That code is incorrect", message: "Please input the correct code", preferredStyle: .alert)
invalidCodeAlert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil))
self.present(invalidCodeAlert, animated: true)
print(error)
return
}
//MARK: User is signed in
print("Phone number: \(String(describing: user?.phoneNumber))")
self.performSegue(withIdentifier: "accountCreated", sender: self)
}
}
}
All help is very much appreciated! Thanks!
Let's say you hold user data at the users ref. When the user signs in check to see if they have any data there, if they do then they are an existing user, if they don't then it is a new account:
Auth.auth().signIn(with: credential, completion: { [weak self](authDataResult, error) in
if let error = error { return }
guard let safeAuthDataResult = authDataResult else { return }
// 1. get the signed in user's userId
let userId = safeAuthDataResult.user.uid
// 2. check to see if their userId exists at the user's path
let usersRef = Database.database().reference().child("users").child(userId)
usersPublicDataRef.observeSingleEvent(of: .value, with: { (snapshot) in
// 3. this is a new user
if !snapshot.exists() {
// *** what you should do here is update the user's ref with some sort of data
} else {
// 4. this is an existing user
}
})
})
You can use FirebaseAuthUI and FirebasePhoneAuthUI to registered your mobile number into firebase. It's default method and UI which is provided by Firebase itself. So you don't have to worry about rest of things.
You just have to installed PhoneAuthUI using pod and write down below code to registered mobile number:
FUIAuth.defaultAuthUI()?.delegate = self
let phoneProvider = FUIPhoneAuth.init(authUI: FUIAuth.defaultAuthUI()!)
FUIAuth.defaultAuthUI()?.providers = [phoneProvider]
let currentlyVisibleController = self.navigationController?.visibleViewController
phoneProvider.signIn(withPresenting: currentlyVisibleController!, phoneNumber: nil)
Once your mobile number is registered then you will get a callback on this method:
func authUI(_ authUI: FUIAuth, didSignInWith user: User?, error: Error?) {
if user != nil{
// here we need to check if current user is registered or not.
var ref: DatabaseReference!
ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
}) { (error) in
print(error.localizedDescription)
}
}else if error != nil{
print(error?.localizedDescription)
}
}
For more information, you can see this tutorial.

Swift FacebookSDK login bug

I face a strange issue using Facebook SDK to implement login.
I have 2 views :
- login view, with a facebook button
- welcome view, with access restricted to logged user
My code in login view :
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if ( FBSDKAccessToken.currentAccessToken() != nil){
let homePage = self.storyboard?.instantiateViewControllerWithIdentifier("SWRevealViewController") as! SWRevealViewController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = homePage
}
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if error != nil {
print(error.localizedDescription)
return
}
if let _:FBSDKAccessToken = result.token {
let homePage = self.storyboard?.instantiateViewControllerWithIdentifier("SWRevealViewController") as! SWRevealViewController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = homePage
}
}
In the welcome view I just put a WELCOME message on the console.
The bug is the following: when my user is not already logged, I see twice WELCOME messages.
That means that 2 threads go to welcome view: one with login button, and another with viewDidload().
What is the best way to implement the login to avoid this issue?
Thank you for your feedback.
EDIT
Welcome View code:
override func viewDidLoad() {
super.viewDidLoad()
if (self.revealViewController() != nil) {
self.menuButton.target = self.revealViewController()
self.menuButton.action = "revealToggle:"
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
print("WELCOME")
}

Passing OAuth token between views programmatically in Swift

I am stuck on trying to pass a variable from one view to another. I am deliberately doing this all programmatically without Story Boards.
ViewController creates has a UIButton which when pressed calls the AuthoriseController.
AuthoriseController then loads a UIWebView, opening it at a particular URL e.g. https://foursquare.com/oauth2/authenticate?client_id=xxxxx&response_type=token&redirect_uri=yyyyy
This loads up an OAuth screen. After the user logs in, control comes back to AuthoriseController where I extract the access_token which is returned in the redirect URI.
The exctraction works great and I can get hold of the token, my problem is that I want to get the the token back to my ViewController so that when the user pushes the button again it will be available to an HTTP request (at the moment I’m just printing the token).
My ViewController and AuthoriseController implement a Protocol called DataProtocol which I was hoping could be used share the token between controllers.
ViewController:
import UIKit
class ViewController: UIViewController {
var delegate: DataProtocol? = nil
func setUpView(){
let firstView = UIView()
let button = UIButton.buttonWithType(UIButtonType.System) as UIButton
firstView.setTranslatesAutoresizingMaskIntoConstraints(false)
firstView.backgroundColor = UIColor(red: 0.75, green: 0.75, blue: 0.1, alpha: 1.0)
view.addSubview(firstView)
setUpButton(button)
firstView.addSubview(button)
setUpConstraints(firstView, button)
}
func setUpConstraints(firstView: UIView, _ button: UIButton) {
let viewsDictionary = ["firstView":firstView,"button":button]
let metricsDictionary = ["firstViewHeight":1334.0,"viewWidth":750.0 ]
let firstViewHeightConstraintH:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("H:[firstView(viewWidth)]", options: NSLayoutFormatOptions(0), metrics: metricsDictionary, views: viewsDictionary)
let firstViewHeightConstraintV:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("V:[firstView(firstViewHeight)]", options: NSLayoutFormatOptions(0), metrics: metricsDictionary, views: viewsDictionary)
firstView.addConstraints(firstViewHeightConstraintH)
firstView.addConstraints(firstViewHeightConstraintV)
let buttonConstraintH:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("H:|-150-[button(>=80)]", options: NSLayoutFormatOptions.AlignAllCenterY, metrics: nil, views: viewsDictionary)
let buttonConstraintV:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("V:|-300-[button]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary)
firstView.addConstraints(buttonConstraintH)
firstView.addConstraints(buttonConstraintV)
}
func setUpButton(button: UIButton) {
button.setTranslatesAutoresizingMaskIntoConstraints(false)
button.setTitle("Authorise", forState: UIControlState.Normal)
button.addTarget(self, action: "buttonPressed", forControlEvents: UIControlEvents.TouchUpInside)
button.backgroundColor = UIColor.blueColor()
button.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
}
override func shouldAutorotate() -> Bool {
return false
}
func buttonPressed(){
let ac = AuthoriseController()
self.presentViewController(ac, animated: true, completion: nil)
println("token in ViewController.buttonPressed: [\(self.delegate?.token)]")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 0.9, green: 0.9, blue: 1, alpha: 1.0)
setUpView()
}
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.All.toRaw())
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
AuthoriseController:
import UIKit
import WebKit
class AuthoriseController: UIViewController, UIWebViewDelegate, DataProtocol {
var token: String = ""
let AUTH_URL = "https://foursquare.com/oauth2/authenticate?client_id=";
let RESP_TYPE_AND_KEY_AND_TOKEN = "&response_type=token&redirect_uri=";
let REDIRECT_URI : String = "ZZZZZZZZ";
let CLIENT_ID : String = "XXXXXXX";
var webview: UIWebView = UIWebView()
override func viewDidLoad() {
super.viewDidLoad()
var myViewController = ViewController()
self.webview.frame = self.view.bounds
self.webview.delegate = self
self.view.addSubview(self.webview)
var url: NSURL = NSURL.URLWithString(AUTH_URL + CLIENT_ID + RESP_TYPE_AND_KEY_AND_TOKEN + REDIRECT_URI)
var urlRequest: NSURLRequest = NSURLRequest(URL: url)
self.webview.loadRequest(urlRequest)
}
func webView(webView: UIWebView!, shouldStartLoadWithRequest request: NSURLRequest!, navigationType: UIWebViewNavigationType) -> Bool {
token = getToken(request.URL.absoluteString!)
println("token in AuthoriseController.webView: [\(token)]")
if(!token.isEmpty) {
self.dismissViewControllerAnimated(true, completion: nil)
}
return true
}
func getToken(url: String) -> String {
var token = ""
if url.rangeOfString("#access_token") != nil{
token = url.componentsSeparatedByString("=").last!
}
return token
}
}
Here is the DataProtocol:
import Foundation
protocol DataProtocol {
var token : String { get set }
}
When I doing this:
Starting the App
Pressing the Authorise button
Successfully logging in (control returns to ViewController
Pressing the button again
Output:I get this output:
token in ViewController.buttonPressed: [nil]
token in AuthoriseController.webView: []
token in AuthoriseController.webView: []
token in AuthoriseController.webView: []
token in AuthoriseController.webView: [VSKRNHJRS3NWPN3EEIYMCSZYJ2YMNSH4GBXNQFL1EMYLJ5TO]
token in ViewController.buttonPressed: [nil]
token in AuthoriseController.webView: []
token in AuthoriseController.webView: [VSKRNHJRS3NWPN3EEIYMCSZYJ2YMNSH4GBXNQFL1EMYLJ5TO]
I’d hoped to see the token in the second occurrence of ViewController.buttonPressed, instead of NIL.
Can anyone see where I am going wrong? Any help is greatly appreciated! Steve
It looks like the delegate is hooked up correctly but you aren't sending the token to it. In AuthoriseController, you need to set the token for the delegate:
func webView(webView: UIWebView!, shouldStartLoadWithRequest request: NSURLRequest!, navigationType: UIWebViewNavigationType) -> Bool {
token = getToken(request.URL.absoluteString!) // sets the token in the AuthoriseController
println("token in AuthoriseController.webView: [\(token)]")
if(!token.isEmpty) {
// pass the token to the delegate
self.delegate?.token = token
self.dismissViewControllerAnimated(true, completion: nil)
}
return true
}