You are currently viewing Google Sign-In Integration in iOS

Google Sign-In Integration in iOS

Since the publish of my article about Facebook login integration, I have received tons of requests that wanted me to cover some other sign-in methods in iOS.

After doing a quick search on Google, I found that most of the blog posts that talk about Google Sign-in integration are quite old and might have been outdated.

Therefore, I think it is good for me to create a step-by-step tutorial on how to integrate Google Sign-in into an iOS app so that anyone who is working on the integration can refer to this article to get a clearer picture.


The Sample App

Throughout this article, I will show you how to configure your Xcode to use the Google Sign-in SDK as well as how to handle UI changes after a user completed the sign-in/sign-out action.

The following animated GIF showcases what we will achieve at the end of this article.

Google Sign-in integration sample app
Google Sign-in integration sample app

With all that being said, let’s buckle up and get right into it!


Google Sign-In SDK Installation

Just like every other integration, we first must install all the required dependencies. For our sample app, we will use CocoaPods to install the Google Sign-in SDK.

Fire up your Terminal app and navigate to the sample app project location. Use the following command to generate a Podfile.

pod init

After that, open the Podfile created for your sample app and add the following:

pod 'GoogleSignIn'

Here’s how your Podfile should look like:

Podfile to install Google Sign-in SDK
Podfile to install Google Sign-in SDK

Save the Podfile, head back to the Terminal app and run the following command:

pod install

After the installation is completed, you should see a workspace file (.xcworkspace) has been created for your sample app. Double click the workspace file and launch your sample app in Xcode.

To ensure the SDK installation is successful, open AppDelegate.swift and import the SDK.

import GoogleSignIn

If your sample app is able to build and run without any errors, then you are good to proceed to the next section.


Creating OAuth Client ID for Google Sign-In

After performing the SDK installation, you will need an OAuth client ID and an iOS URL scheme so that you can continue with the configuration in Xcode.

Head over to Google APIs Console and create a project for your sample app. If you have already created a project, you can also select it from the project list.

Create project in Google APIs console
Create project in Google APIs console

After you have created/selected a project, you also need to configure the OAuth consent screen. Follow the steps as shown in the image below to choose your app user type and then create the OAuth consent screen.

Create OAuth consent screen in Google APIs console
Create OAuth consent screen in Google APIs console

In the OAuth consent screen information page, fill in the application name, and then click “save”. This application name will be the name being shown in the Google Sign-in form when a user tries to sign in using your app.

Application name in OAuth consent screen information page
Application name in OAuth consent screen information page

Once finished configuring the OAuth consent screen, it is time to create the OAuth client ID. You can follow the steps as shown in the image below to navigate to the OAuth client ID creation page.

Create OAuth client ID in Google APIs console
Create OAuth client ID in Google APIs console

Once you reach the OAuth client ID creation page, go ahead and select “iOS” as application type, fill in the name and also your sample app bundle ID and then click “create”.

Create OAuth client ID in Google APIs console
Create OAuth client ID

Copy the OAuth client ID and iOS URL scheme you just create and keep them in somewhere easily reachable, you will need both of them in just a bit.

Copy OAuth client ID and iOS URL scheme in Google APIs console
Copy OAuth client ID and iOS URL scheme

With that you have completed the OAuth client ID creation process, we can now head back to Xcode and proceed with the configuration.


Google Sign-In SDK Configuration in Xcode

Google Sign-in requires a custom URL Scheme to be added to your project. To add the custom scheme, follow the steps shown in the image below, and fill in the iOS URL scheme that you obtained while creating the OAuth client ID.

Fill in URL scheme in Xcode to configure Google Sign-in SDK
Fill in URL scheme in Xcode

Next, open AppDelegate.swift and import the GoogleSignIn module.

import GoogleSignIn

After that, add the following 3 lines of code into application(_:didFinishLaunchingWithOptions:).

// 1
GIDSignIn.sharedInstance().clientID = "[OAuth_Client_ID]"
// 2
GIDSignIn.sharedInstance().delegate = self
// 3
GIDSignIn.sharedInstance()?.restorePreviousSignIn()
  1. Configure GIDSignIn with the client ID. Make sure to replace [OAuth_Client_ID] with your actual OAuth client ID.
  2. Make self as delegate of GIDSignIn so that we will get notified when the sign-in process is completed. We will implement the delegate method in just a bit.
  3. In case the user has signed in before, we will restore the sign-in state during app launches.

Next, add the following AppDelegate method implementation right after application(_:didFinishLaunchingWithOptions:).

func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {

    return GIDSignIn.sharedInstance().handle(url)
}

Note:

I found that the above AppDelegate method is not being triggered for the entire login process. However, based on Google’s official documentation, this implementation is required. Therefore, to make sure the sign-in integration is future proof, I would recommend everyone to just include this implementation regardless.

Lastly, let’s conform AppDelegate to GIDSignInDelegate and implement the sign(_:didSignInFor:withError:) method.

The sign(_:didSignInFor:withError:) method that we are going to implement is being triggered every time when the sign-in process is completed. Therefore, we will leverage this delegate method to post a signInGoogleCompleted notification every time a user successfully signed in.

extension AppDelegate: GIDSignInDelegate {
    
    func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
        
        // Check for sign in error
        if let error = error {
            if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
                print("The user has not signed in before or they have since signed out.")
            } else {
                print("\(error.localizedDescription)")
            }
            return
        }

        // Post notification after user successfully sign in
        NotificationCenter.default.post(name: .signInGoogleCompleted, object: nil)
    }
}

// MARK:- Notification names
extension Notification.Name {
    
    /// Notification when user successfully sign in using Google
    static var signInGoogleCompleted: Notification.Name {
        return .init(rawValue: #function)
    }
}

Here’s how your AppDelegate.swift should look like after all the implementations.

import UIKit
import GoogleSignIn

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // Initialize Google sign-in
        GIDSignIn.sharedInstance().clientID = "[OAuth_Client_ID]"
        GIDSignIn.sharedInstance().delegate = self

        // If user already sign in, restore sign-in state.
        GIDSignIn.sharedInstance()?.restorePreviousSignIn()
        
        return true
    }
    
    func application(_ app: UIApplication,
                     open url: URL,
                     options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
        
        return GIDSignIn.sharedInstance().handle(url)
    }
}

extension AppDelegate: GIDSignInDelegate {
    
    func sign(_ signIn: GIDSignIn!,
              didSignInFor user: GIDGoogleUser!,
              withError error: Error!) {
        
        // Check for sign in error
        if let error = error {
            if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
                print("The user has not signed in before or they have since signed out.")
            } else {
                print("\(error.localizedDescription)")
            }
            return
        }

        // Post notification after user successfully sign in
        NotificationCenter.default.post(name: .signInGoogleCompleted, object: nil)
    }
}

// MARK:- Notification names
extension Notification.Name {
    
    /// Notification when user successfully sign in using Google
    static var signInGoogleCompleted: Notification.Name {
        return .init(rawValue: #function)
    }
}

With that, we have completed the SDK configuration. In the following section, we will start working on the sign-in/sign-out workflow.


Sign-in/Sign-out Workflow Implementation

Let’s start the workflow implementation by adding all the required UI elements into the view controller.

UI Implementation

As a recap, here’s the UI that we will be creating.

Google Sign-in integration sample iOS app UI
Sample App UI

For simplicity’s sake, I will add all the UI elements into the view controller programmatically. Feel free to use storyboard if that is your preferred way of creating UI.

For the following sample code, except for all the UI elements related code, there are 2 other things that you should be aware of:

  1. Do remember to import theGoogleSignIn module before the view controller implementation.
  2. At the end of viewDidLoad() implementation, assign self as the presenter of the Google sign-in sheet.
// import GoogleSignIn module
import GoogleSignIn

class ViewController: UIViewController {
    
    var signInButton: UIButton!
    var signOutButton: UIButton!
    var greetingLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Add greeting label
        greetingLabel = UILabel()
        greetingLabel.text = "Please sign in... 🙂"
        greetingLabel.textAlignment = .center
        greetingLabel.backgroundColor = .tertiarySystemFill
        view.addSubview(greetingLabel)
        greetingLabel.translatesAutoresizingMaskIntoConstraints = false
        greetingLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        greetingLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -80).isActive = true
        greetingLabel.heightAnchor.constraint(equalToConstant: 54).isActive = true
        greetingLabel.widthAnchor.constraint(equalToConstant: 300).isActive = true
        
        // Add sign-in button
        signInButton = UIButton()
        signInButton.layer.cornerRadius = 10.0
        signInButton.setTitle("Sign in with Google", for: .normal)
        signInButton.setTitleColor(.white, for: .normal)
        signInButton.backgroundColor = .systemRed
        signInButton.addTarget(self, action: #selector(signInButtonTapped(_:)), for: .touchUpInside)
        view.addSubview(signInButton)
        signInButton.translatesAutoresizingMaskIntoConstraints = false
        signInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        signInButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        signInButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
        signInButton.widthAnchor.constraint(equalToConstant: 250).isActive = true
        
        // Add sign-out button
        signOutButton = UIButton()
        signOutButton.layer.cornerRadius = 10.0
        signOutButton.setTitle("Sign Out 👋", for: .normal)
        signOutButton.setTitleColor(.label, for: .normal)
        signOutButton.backgroundColor = .systemFill
        signOutButton.addTarget(self, action: #selector(signOutButtonTapped(_:)), for: .touchUpInside)
        view.addSubview(signOutButton)
        signOutButton.translatesAutoresizingMaskIntoConstraints = false
        signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        signOutButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 80).isActive = true
        signOutButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
        signOutButton.widthAnchor.constraint(equalToConstant: 150).isActive = true
        
        // Sign-out button is hidden by default
        signOutButton.isHidden = true

        // Let GIDSignIn know that this view controller is presenter of the sign-in sheet
        GIDSignIn.sharedInstance()?.presentingViewController = self
    }
}

Pro Tip:

Use system colors and semantic colors during UI implementation to easily adapt to iOS dark mode introduced in iOS 13. Check out this article to learn more.

After adding all the required UI elements, we can continue with the implementation of the sign-in and sign-out button action.

// MARK:- Button action
@objc func signInButtonTapped(_ sender: UIButton) {
    GIDSignIn.sharedInstance()?.signIn()
}

@objc func signOutButtonTapped(_ sender: UIButton) {
    GIDSignIn.sharedInstance()?.signOut()
}

Both of the implementations are quite straightforward, we just need to call the GIDSignIn‘s signIn() and signOut() method accordingly to kick start the sign-in/sign-out workflow.

Handle Sign-in/Sign-out Status Change

The last bit of work that still remains in our sample app is to make sure that the UI will update based on the sign-in/sign-out status change.

Let’s start by implementing an updateScreen() function that changes the UI based on the app current sign-in status.

private func updateScreen() {
    
    if let user = GIDSignIn.sharedInstance()?.currentUser {
        // User signed in
        
        // Show greeting message
        greetingLabel.text = "Hello \(user.profile.givenName!)! ✌️"
        
        // Hide sign in button
        signInButton.isHidden = true
        
        // Show sign out button
        signOutButton.isHidden = false
        
    } else {
        // User signed out
        
        // Show sign in message
         greetingLabel.text = "Please sign in... 🙂"
         
         // Show sign in button
         signInButton.isHidden = false
         
         // Hide sign out button
         signOutButton.isHidden = true
    }
}

Note that we are using the availability of GIDSignIn‘s currentUser to determine whether the user is signed in or signed out. If the user is not signed in, currentUser will be nil.

Pro Tip:

The GIDSignIn‘s currentUser is an instance of GIDGoogleUser. You can use it to get all the user information such as user ID, user’s full name, email, and authentication token.

With the updateScreen() function in place, we can now use it to update the UI after a user successfully signed in.

We will utilize the signInGoogleCompleted notification to update the UI. Thus, go ahead and add the view controller as an observer of the notification and implement the notification’s selector accordingly.

override func viewDidLoad() {
    
    // ...
    // ...
    // ...
    
    // Register notification to update screen after user successfully signed in
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(userDidSignInGoogle(_:)),
                                           name: .signInGoogleCompleted,
                                           object: nil)
}

// MARK:- Notification
@objc private func userDidSignInGoogle(_ notification: Notification) {
    // Update screen after user successfully signed in
    updateScreen()
}

To handle UI change after a user successfully signs out, you can call updateScreen() at the sign-out button action.

@objc func signOutButtonTapped(_ sender: UIButton) {
    GIDSignIn.sharedInstance()?.signOut()
    
    // Update screen after user successfully signed out
    updateScreen()
}

Lastly, call the updateScreen() function at viewDidLoad() so that the signed-in UI will be shown after the app launches if the user has previously signed in.

override func viewDidLoad() {
    
    // ...
    // ...
    // ...
    
    // Update screen base on sign-in/sign-out status (when screen is shown)
    updateScreen()
}

Here’s the view controller full implementation.

import UIKit
import GoogleSignIn

class ViewController: UIViewController {
    
    var signInButton: UIButton!
    var signOutButton: UIButton!
    var greetingLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Add greeting label
        greetingLabel = UILabel()
        greetingLabel.text = "Please sign in... 🙂"
        greetingLabel.textAlignment = .center
        greetingLabel.backgroundColor = .tertiarySystemFill
        view.addSubview(greetingLabel)
        greetingLabel.translatesAutoresizingMaskIntoConstraints = false
        greetingLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        greetingLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -80).isActive = true
        greetingLabel.heightAnchor.constraint(equalToConstant: 54).isActive = true
        greetingLabel.widthAnchor.constraint(equalToConstant: 300).isActive = true
        
        // Add sign in button
        signInButton = UIButton()
        signInButton.layer.cornerRadius = 10.0
        signInButton.setTitle("Sign in with Google", for: .normal)
        signInButton.setTitleColor(.white, for: .normal)
        signInButton.backgroundColor = .systemRed
        signInButton.addTarget(self, action: #selector(signInButtonTapped(_:)), for: .touchUpInside)
        view.addSubview(signInButton)
        signInButton.translatesAutoresizingMaskIntoConstraints = false
        signInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        signInButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        signInButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
        signInButton.widthAnchor.constraint(equalToConstant: 250).isActive = true
        
        // Add sign out button
        signOutButton = UIButton()
        signOutButton.layer.cornerRadius = 10.0
        signOutButton.setTitle("Sign Out 👋", for: .normal)
        signOutButton.setTitleColor(.label, for: .normal)
        signOutButton.backgroundColor = .systemFill
        signOutButton.addTarget(self, action: #selector(signOutButtonTapped(_:)), for: .touchUpInside)
        view.addSubview(signOutButton)
        signOutButton.translatesAutoresizingMaskIntoConstraints = false
        signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        signOutButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 80).isActive = true
        signOutButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
        signOutButton.widthAnchor.constraint(equalToConstant: 150).isActive = true
        
        // Sign out button is hidden by default
        signOutButton.isHidden = true
        
        // Let GIDSignIn know that this view controller is presenter of the sign-in sheet
        GIDSignIn.sharedInstance()?.presentingViewController = self
        
        // Register notification to update screen after user successfully signed in
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(userDidSignInGoogle(_:)),
                                               name: .signInGoogleCompleted,
                                               object: nil)
        
        // Update screen base on sign-in/sign-out status (when screen is shown)
        updateScreen()
    }
    
    private func updateScreen() {
        
        if let user = GIDSignIn.sharedInstance()?.currentUser {
            // User signed in
            
            // Show greeting message
            greetingLabel.text = "Hello \(user.profile.givenName!)! ✌️"
            
            // Hide sign in button
            signInButton.isHidden = true
            
            // Show sign out button
            signOutButton.isHidden = false
            
        } else {
            // User signed out
            
            // Show sign in message
             greetingLabel.text = "Please sign in... 🙂"
             
             // Show sign in button
             signInButton.isHidden = false
             
             // Hide sign out button
             signOutButton.isHidden = true
        }
    }
    
    // MARK:- Button action
    @objc func signInButtonTapped(_ sender: UIButton) {
        GIDSignIn.sharedInstance()?.signIn()
    }

    @objc func signOutButtonTapped(_ sender: UIButton) {
        GIDSignIn.sharedInstance()?.signOut()
        
        // Update screen after user successfully signed out
        updateScreen()
    }

    // MARK:- Notification
    @objc private func userDidSignInGoogle(_ notification: Notification) {
        // Update screen after user successfully signed in
        updateScreen()
    }
}

With that, we have successfully integrated Google Sign-in into our sample app. 🥳

Build and run the sample app to see everything in action.


What’s Next?

If you would like to extend the Google Sign-in functionality by integrating it with Firebase Authentication, check out the following article which is the continuation of this article.

🔗 Integrate Google Sign-In with Firebase Authentication


Wrapping Up

I hope this article can give you a clear idea on how to integrate Google Sign-in into your iOS app.

I will be covering some other topics related to Google Sign-in integration in the near future. If you would like to get notified when a new article comes out, you can follow me on Twitter and subscribe to my monthly newsletter.

Thanks for reading and happy coding! 👨🏼‍💻


Further Readings


👋🏻 Hey!

While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.

  • Bartender: Superpower your menu bar and take full control over your menu bar items.
  • CleanShot X: The best screen capture app I’ve ever used.
  • PixelSnap: Measure on-screen elements with ease and precision.
  • iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.