You are currently viewing Creating Custom Facebook Login Button in Swift

Creating Custom Facebook Login Button in Swift

To add a Facebook login button into your app, it can be as easy as using FBLoginButton which included in the Facebook SDK. However, not much customization can be made on the appearance of FBLoginButton.

If you need a Facebook login button that matches your app’s look and feel, you will have to create your own custom Facebook Login Button. Furthermore, you also need to implement all the necessary API calls all by yourself.

In this article, I will go through how you can add the Facebook login functionality to your custom button. After that, we will look into how you can make your custom Facebook login button reusable in your other Xcode projects.

Note that I will not dive into how to setup an Xcode project to use Facebook SDK. If you have not properly setup your Xcode project, I recommend you to go through the setup process by referring to my previous article before proceeding with this article.

🔗 Step-by-Step: Facebook Login Integration in Swift


The Overview

For demo purposes, I will create a custom login button that shows text with emoji, implement all necessary login / logout functionalities and shows different title message based on current login status.

Following animated GIF showcases what we will achieve in this article.

Login using custom Facebook button
Login using custom Facebook button

With all that being said, let’s fire up your Xcode and get started. 🔨


Adding Custom Facebook Login Button

Before we start working on the custom login button, let’s import FBSDKLoginKit to you view controller.

import FBSDKLoginKit

We will be using storyboard for our UI implementation.

First, add a button and label into your view controller. Create 2 IBOutlets loginButton and messageLabel and connect them to the label and button you just added.

Next, add a “touch up inside” IBAction named loginButtonTapped(_:) and connect it to the loginButton.

Connecting code to storyboard when create custom Facebook login button
Connecting code to storyboard

After that, we will need 2 private functions which in charge of updating the loginButton and messageLabel text based on current login status.

extension CustomButtonViewController {
    
    private func updateButton(isLoggedIn: Bool) {
        // 1
        let title = isLoggedIn ? "Log out 👋🏻" : "Log in 👍🏻"
        loginButton.setTitle(title, for: .normal)
    }
    
    private func updateMessage(with name: String?) {
        // 2
        guard let name = name else {
            // User already logged out
            messageLabel.text = "Please log in with Facebook."
            return
        }
        
        // User already logged in
        messageLabel.text = "Hello, \(name)!"
    }
}
  1. For the custom login button’s text, show “Log in 👍🏻” when user is logged out, else show “Log out 👋🏻”.
  2. For title message, show “Please log in with Facebook.” when user is logged out, else show greeting message with user’s name.

In order for both loginButton and messageLabel to show the correct information when we launch the screen, we need to call both of these functions in viewDidLoad().

override func viewDidLoad() {
    super.viewDidLoad()
    
    // 1
    updateButton(isLoggedIn: (AccessToken.current != nil))
    
    // 2
    updateMessage(with: Profile.current?.name)
}
  1. Use the existence of access token to determine current login status and pass it into the updateButton(isLoggedIn:) function. When AccessToken.current is nil, means that the user is currently logged out.
  2. Retrieve user’s name from Facebook SDK’s Profile class’s static variable and pass it into updateMessage(with:) function to show title message accordingly.

Lastly, we will need to implement the core functionality of this view controller — login and logout. Following is the implementation of loginButtonTapped(_:) IBAction.

@IBAction func loginButtonTapped(_ sender: Any) {
    
    // 1
    let loginManager = LoginManager()
    
    if let _ = AccessToken.current {
        // Access token available -- user already logged in
        // Perform log out
        
        // 2
        loginManager.logOut()
        updateButton(isLoggedIn: false)
        updateMessage(with: nil)
        
    } else {
        // Access token not available -- user already logged out
        // Perform log in
        
        // 3
        loginManager.logIn(permissions: [], from: self) { [weak self] (result, error) in
            
            // 4
            // Check for error
            guard error == nil else {
                // Error occurred
                print(error!.localizedDescription)
                return
            }
            
            // 5
            // Check for cancel
            guard let result = result, !result.isCancelled else {
                print("User cancelled login")
                return
            }
          
            // Successfully logged in
            // 6
            self?.updateButton(isLoggedIn: true)
            
            // 7
            Profile.loadCurrentProfile { (profile, error) in
                self?.updateMessage(with: Profile.current?.name)
            }
        }
    }
}
  1. Create an instance of Facebook SDK’s LoginManager class to handle login and logout operations.
  2. When access token is available, perform logout operation and update the text of both loginButton and messageLabel.
  3. Access token not available, thus perform login operation. Note that the permissions parameter is not required when we only accessing basic Facebook profile information. Therefore, we only need to pass in an empty array.
  4. Check for error after login operation completed. If error occurred, we will print out the error and return.
  5. Check if user cancelled the login operation. If so, nothing else needs to be done, thus just return.
  6. Successfully logged in, thus update the text of the custom login button.
  7. Use the Profile class’s loadCurrentProfile(completion:) method to retrieve the logged in user’s Facebook profile information and update the title message.

After finish implementing loginButtonTapped(_:), it is time to test out the login and logout operations.

Since your have not make your Facebook app available in production, you will need a test user to test out the login and logout operations.

Check out the “Test Facebook Login Integration Using Test Users” section of Step-by-Step: Facebook Login Integration in Swift to learn how you can setup a test user.

Once you have created a test user, build and run your app. If you followed all the implementation correctly, you should be able to perform login and logout without any problem.


Making Custom Facebook Login Button Reusable

What differentiates great developers from good developers, is that great developers will make their code as modular as possible, so that it can be reused in other projects.

For our case, the custom Facebook login button is a very good candidate for refactoring in order to make it reusable.

Following are what we are trying to achieve from the refactoring:

  1. Create a UIButton subclass.
  2. Define login and logout completion handlers.
  3. Encapsulate login and logout logic within the login button.

Create a UIButton Subclass

Let’s go ahead and create a UIButton subclass, name it “MYFacebookLoginButton” and make sure to import FBSDKLoginKit into MYFacebookLoginButton.

First, add the updateButton(isLoggedIn:) method that we previously implemented as a private function. Also, define a commonSetup() method that we will be implementing shortly.

extension MYFacebookLoginButton {
    
    private func commonSetup() {
 
    }
    
    private func updateButton(isLoggedIn: Bool) {
        let title = isLoggedIn ? "Log out 👋🏻" : "Log in 👍🏻"
        setTitle(title, for: .normal)
    }
}

Next we will need to implement 2 init methods for the MYFacebookLoginButton.

  1. init(frame:) → Required for initialization using code.
  2. init?(coder:) → Required for initialization using storyboard.
override public init(frame: CGRect) {
    super.init(frame: frame)
    commonSetup()
}

required public init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    commonSetup()
}

As you can see, we will call commonSetup() every time MYFacebookLoginButton being initialized to perform any necessary configuration.

Here’s the implementation of commonSetup().

private func commonSetup() {
    
    // 1
    // Set button title
    updateButton(isLoggedIn: (AccessToken.current != nil))
    
    // 2
    responsibleViewController = findResponsibleViewController()
    
    // 3
    // Override touch up inside event
    addTarget(self, action: #selector(touchUpInside(sender:)), for: .touchUpInside)
}
  1. Update MYFacebookLoginButton‘s text based on current login status.
  2. Use findResponsibleViewController() to get reference of view controller that responsible to (own) the MYFacebookLoginButton instance, we will need the responsible view controller when we implement the login logic later. Do note that this method is not yet implemented, we will get into that later.
  3. Add MYFacebookLoginButton as target to handle its “touch up inside” event.

You might notice that we have not defined the responsibleViewController variable. Let’s go ahead and define it. Note that we will define it as weak variable to avoid retain cycle.

private weak var responsibleViewController: UIViewController!

Next up, let’s look into the implementation of findResponsibleViewController() method.

extension UIView {
    
    /// Find the view controller that responsible for a particular view
    func findResponsibleViewController() -> UIViewController? {
        if let nextResponder = self.next as? UIViewController {
            return nextResponder
        } else if let nextResponder = self.next as? UIView {
            return nextResponder.findResponsibleViewController()
        } else {
            return nil
        }
    }
}

As shown in the code snippet above, it is a UIView extension that recursively go through a view’s stack to look for its responsible view controller. For more information, you can refer to this article.

Since UIButton is a subclass of UIView, thus we can utilize the findResponsibleViewController() method in our case.

Define Login and Logout Completion Handlers

We will define 2 closures as handler for both login and logout completion. Both of these handlers will trigger after user successfully login or logout using Facebook.

var loginCompletionHandler: ((MYFacebookLoginButton, Result<LoginManagerLoginResult, Error>) -> Void)?
var logoutCompletionHandler: ((MYFacebookLoginButton) -> Void)?

Passing the button itself to both handlers is generally a good practice, this can avoid accidental retain cycles if the button is used within one of its own completion handlers and we forget to capture it weakly.

For login completion handler, there will be another extra parameter, which is the login result return by Facebook SDK. Here we will be using Result type introduced in Swift 5 to pass back either an instance of LoginManagerLoginResult or an instance of Error.

If you want to know more about Result type, check out this article.

Encapsulate Login and Logout Logic Within the Login Button

To encapsulate the login and logout logic within the login button, we will implement the “touch up inside” event handler within MYFacebookLoginButton. The logic is very similar to the loginButtonTapped(_:) IBAction that we previously implemented.

@objc private func touchUpInside(sender: MYFacebookLoginButton) {
    
    let loginManager = LoginManager()
    
    if let _ = AccessToken.current {
        // Access token available -- user already logged in
        // Perform log out
        
        loginManager.logOut()
        updateButton(isLoggedIn: false)
        
        // 1
        // Trigger logout completed handler
        logoutCompletionHandler?(self)
        
    } else {
        // Access token not available -- user already logged out
        // Perform log in
        
        // 2
        loginManager.logIn(permissions: [], from: responsibleViewController) { [weak self] (result, error) in
            
            // 3
            // Check for error
            guard error == nil else {
                // Error occurred
                print(error!.localizedDescription)
                
                if let self = self {
                    self.loginCompletionHandler?(self, .failure(error!))
                }
                
                return
            }
            
            // Check for cancel
            guard let result = result, !result.isCancelled else {
                print("User cancelled login")
                return
            }
            
            // Successfully logged in
            self?.updateButton(isLoggedIn: true)
            
            // 4
            // Trigger login completed handler
            if let self = self {
                self.loginCompletionHandler?(self, .success(result))
            }
        }
    }
}

There are a few places that we need to take note here:

  1. Trigger the logout completion handler after logout completed.
  2. Pass in responsibleViewController when calling login() method of loginManager.
  3. Trigger the login completion handler after login completed with error. There are 2 things to take note here. Since we are capturing self weakly within the loginManager‘s completion handler, thus we will use optional binding to check the availability of self. Another thing to take note is that we are passing the error object as the failure case of Result type.
  4. Trigger the login completion handler after login completed successfully. Here, we pass the result object as the success case of Result type.

With that, we have completed the implementation of the reusable custom Facebook login button.


Using the Reusable Custom Facebook Login Button

There are 2 ways you can add the reusable button to your view controller:

  1. Using code (programatically)
  2. Using storyboard

To programatically add the button to your view controller, paste below code snippet to your view controller’s viewDidLoad() method.

let fbButton = MYFacebookLoginButton(frame: CGRect(x: 10, y: 200, width: 100, height: 50))
fbButton.backgroundColor = .black
view.addSubview(fbButton)

If you prefer to use storyboard, just add a UIButton to your view controller and set its class to MYFacebookLoginButton. Then make an IBOutlet connection to your view controller.

Changing a button's class in Xcode
Setting button class to MYFacebookLoginButton

Lastly, let’s look at how to use the login and logout completion handler to update the messageLabel. Add the following code to your view controller’s viewDidLoad() method.

// Implement login completion handler
loginButton.loginCompletionHandler = { [weak self] (button, result) in
    switch result {
    case .success(let result):
        print("Access token: \(String(describing: result.token?.tokenString))")
        
        // 1
        // Show message after login completed
        Profile.loadCurrentProfile { (profile, error) in
            self?.updateMessage(with: Profile.current?.name)
        }
    case .failure(let error):
        // 2
        print("Error occurred: \(error.localizedDescription)")
        break
    }
}

// Implement logout completion handler
loginButton.logoutCompletionHandler = { [weak self] (button) in
    // 3
    // Show message after logout completed
    self?.updateMessage(with: nil)
}
  1. Handle success case of the Result type. Use the Profile class’s loadCurrentProfile(completion:) method to retrieve the logged in user’s Facebook profile information and update the title message.
  2. Handle failure case of the Result type.
  3. Update the title message after user successfully logged out.

Note that the updateMessage(with:) method that being called here is exactly the same as the one we previously implemented.

That’s about it! You can now build and run your app to see the reusable custom Facebook login button in action. 🤟🏻


Wrapping Up

We have only implemented the basic login and logout functionalities for our reusable Facebook login button, you can most definitely improve it by extending its functionalities.

Feel free to download the full sample project on Github and add in your desired functionalities.

Further Reading


I hope you find this article helpful. If you like this article, feel free to share it with your friends and leave me a comment.

You can follow me on Twitter for more article related to iOS development.

Thanks for reading! 👨🏼‍💻


👋🏻 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.