實作 iOS App 的 Facebook 登入功能

在這個幾乎人人都有 FB 帳號的時代,很多 iOS App 都有 FB 登入功能,讓使用者不用再麻煩地註冊帳號密碼。

接下來就讓我們一步步實現 FB 登入功能吧。

FB iOS SDK 的官方文件

喜歡照著官方說明一步步操作的朋友,可參考以下連結。

懶得看官方說明的朋友,則可跟著彼得潘一步步實現 FB 登入功能。

連到 Facebook 開發網站

建立 App

為了在 iOS App 加入 FB 登入功能,我們得先在 FB 開發網站上建立一個對應的 App。

  • 點選右上方的 My Apps。
  • 點選右上的 Create App。

選擇 App 的類型,一般我們會選擇 Consumer,比方 FB Login 功能即屬於 Consumer 類型。

輸入 App 名稱後點選右下的 Create App。

在 App 頁面點選 Facebook Login 下的 Set up

點選 iOS

點選 Next。

輸入 Bundle ID

可從 TARGETS App 的 General 頁面查詢 Bundle ID 。

將 Single Sign On 設為 Yes

設定 Info 頁面

切換到 App 的 Info 頁面,找到 URL Types 區塊,點選 + 新增 URL Type。

在 URL Schemes 輸入剛剛在 Facebook 開發網站建立的 App 的 App ID,格式為 fb 後接 App ID。

App ID 可從 FB 開發網站上的 App 頁面查詢。

在 Info 頁面新增 FacebookAppID & FacebookDisplayName,內容一樣來自 FB 開發網站的 App 頁面。

在 Info 頁面新增 FacebookClientToken,內容一樣來自 FB 開發網站的 App 頁面。

FacebookClientToken 可從 FB 開發網站上的 App 頁面查詢,從 Settings > Advanced 的 Client token 欄位。

在 App 的 Info 頁面新增型別 array 的 LSApplicationQueriesSchemes,包含 fbapi & fb-messenger-share-api ,讓 App 之後能啟動 FB App。

打開 Keychain Sharing 的功能

切換到 TARGETS App 的 Signing & Capabilities 頁面,點選 + 新增功能。

加入 Keychain Sharing。

利用 SPM 安裝 Facebook iOS SDK

想在 App 裡加入 FB 登入功能,我們必須先將 Facebook iOS SDK 加到 Xcode 專案。

安裝 Facebook iOS SDK 的方法很多,比方 Cocoapods & SPM。由於 Xcode 原生支援 SPM,接下來我們將利用 SPM 安裝。

新增套件時輸入 FB 套件的網址。

https://github.com/facebook/facebook-ios-sdk

找到 Facebook SDK for iOS 後,在 Up to Next Major Version 輸入 13.0.0,然後點選 Add Package。(ps: 由於目前最新版是 13.2,因此輸入 13.0.0)

勾選 FacebookLogin。

完成套件的安裝,此時 Project navigator 的 Swift Package Dependencies 下將長出 Facebook。

在 App 啟動時完成 FB 的相關設定

我們希望 App 一啟動就能完成 FB 的相關設定,以下分為 SwiftUI 跟 UIKit 版本的寫法。

SwiftUI App

寫法 1: 在遵從 protocol App 的型別裡定義 init

import SwiftUI
import FacebookCore
@main
struct DemoApp: App {

init() {
ApplicationDelegate.shared.application(UIApplication.shared)
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}

說明

import FacebookCore

加入 FacebookCore 後才能使用 FB 的相關程式。

ApplicationDelegate.shared.application(UIApplication.shared)

呼叫 ApplicationDelegate.shared.application(UIApplication.shared),完成 FB 的相關設定。

寫法 2: 定義 AppDelegate

  • 手動新增 AppDelegate.swift,輸入以下程式。
import FacebookCore

class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
return true
}

}

說明

import FacebookCore

加入 FacebookCore 後才能使用 FB 的相關程式。

ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)

呼叫 ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions),完成 FB 的相關設定。

  • 在遵從 protocol App 的型別裡加入以下程式
import SwiftUI@main
struct DemoApp: App {

@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

它將讓 SwiftUI 生成 AppDelegate,讓 AppDelegate 的 function 在某些事件發生時被觸發。

UIKit App

在 AppDelegate 加入以下程式。

import UIKit
import FacebookCore
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

ApplicationDelegate.shared.application(app, open: url, options: options)

}

說明

import FacebookCore

加入 FacebookCore 後才能使用 FB 的相關程式。

ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)

呼叫 ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions),完成 FB 的相關設定。

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

ApplicationDelegate.shared.application(app, open: url, options: options)

}

如果有切換到 FB App,再從 FB App 切換回我們的 App 時會呼叫 application(_:open:options:),我們在裡面執行 ApplicationDelegate.shared 的 application(_:open: url:options:)。

在 openURL function 加入 FB 相關程式

處理從 FB App 切換回我們 App 時回傳的資料。以下分為 SwiftUI 跟 UIKit 版本的寫法。

SwiftUI 版本

在遵從 protocol App 的型別裡加入 onOpenURL modifier,如果有切換到 FB App,再從 FB App 切換回我們的 App 時會呼叫 onOpenURL。(ps: 記得要 import FacebookCore)

import SwiftUI
import FacebookCore
@main
struct DemoApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
ApplicationDelegate.shared.application(UIApplication.shared, open: url, sourceApplication: nil, annotation: UIApplication.OpenURLOptionsKey.annotation)
}

}
}
}

UIKit 版本

在 SceneDelegate 加入以下程式。如果有切換到 FB App,再從 FB App 切換回我們的 App 時會呼叫 scene(_:openURLContexts:)。(ps: 記得要 import FacebookCore)

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else {
return
}
ApplicationDelegate.shared.application(
UIApplication.shared,
open: url,
sourceApplication: nil,
annotation: [UIApplication.OpenURLOptionsKey.annotation]
)

}

FB 登入程式

以下我們先說明它的原理,然後再介紹如何套用在 SwiftUI 跟 UIKit。

生成 LoginManager 物件,透過它的 function logIn 登入,當 closure 參數 result 等於 success 時表示登入成功。(ps: 使用 LoginManager 前要先 import FacebookLogin)

let manager = LoginManager()
manager.logIn { result in
switch result {
case .success(granted: let granted, declined: let declined, token: let token):
print("success")
case .cancelled:
print("cancelled")
case .failed(_):
print("failed")
}
}

function logIn 的宣告如下

func logIn(permissions: [Permission] = [.publicProfile],
viewController: UIViewController? = nil,
completion: LoginResultBlock? = nil)

參數說明:

  • permissions: 傳入 App 想要的權限,比方 publicProfile 表示想取得使用者 FB 的公開資訊。若需要更多權限,可參考下圖自動完成列出的選項。

值得注意的,若想要 public_profile & email 以外的權限,還須經過 FB 的審核。除非有特別需求,不然建議只要求 publicProfile & email 即可。

  • viewController: 負責 present(顯示) FB 登入畫面的 controller。預設為 nil,表示由 topmost view controller present(顯示) FB 登入畫面。
  • completion: FB 登入完成後將執行 completion 的 closure 程式,我們可由型別 LoginResult 的參數判斷結果,當它等於 success 時表示登入成功。

另外由於參數 permissions 的預設值是 [.publicProfile],viewController 的預設值是 nil,若我們採用預設值,則可省略它們,在 login 後直接以 { } 傳入參數 completion 的 closure 程式。

let manager = LoginManager()
manager.logIn { result in
switch result {
case .success(granted: let granted, declined: let declined, token: let token):
print("success")
case .cancelled:
print("cancelled")
case .failed(_):
print("failed")
}
}

了解 FB 登入的原理後,以下分別為它們套用在 SwiftUI & UIKit 的範例。

SwiftUI 登入

import SwiftUI
import FacebookLogin
struct ContentView: View {
var body: some View {
Button {
let manager = LoginManager()
manager.logIn { result in
switch result {
case .success(granted: let granted, declined: let declined, token: let token):
print("success")
case .cancelled:
print("cancelled")
case .failed(_):
print("failed")
}
}
} label: {
Text("Login")
}

}
}

UIKit controller 登入

import UIKit
import FacebookLogin
class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}

@IBAction func login(_ sender: Any) {
let manager = LoginManager()
manager.logIn { result in
switch result {
case .success(granted: let granted, declined: let declined, token: let token):
print("success")
case .cancelled:
print("cancelled")
case .failed(_):
print("failed")
}
}
}

}

測試 App 的 FB 登入

FB 登入後使用者將維持登入狀態,就算我們重新啟動 App ,使用者還是能保持登入。

檢查使用者的登入狀態

當 AccessToken.current 有值時即代表使用者已登入,從 AccessToken 物件的 userID 可取得使用者的 ID。

以下分別示範在 SwiftUI & UIKit 畫面出現時檢查是否登入的範例。

SwiftUI

import SwiftUI
import FacebookLogin
struct ContentView: View {
var body: some View {
if let accessToken = AccessToken.current,
!accessToken.isExpired {
Text("\(accessToken.userID) 登入")
} else {
Text("未登入")
}

}
}

UIKit

import UIKit 
import FacebookLogin
class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.

if let accessToken = AccessToken.current,
!accessToken.isExpired {
print("\(accessToken.userID) login")
} else {
print("not login")
}

}

讀取使用者的 FB profile 資訊

從 Profile 呼叫 function loadCurrentProfile 下載使用者的 profile 資訊,比方使用者的名字跟大頭照網址。

if let _ = AccessToken.current {
Profile.loadCurrentProfile { profile, error in
if let profile = profile {
print(profile.name)
print(profile.imageURL(forMode: .square, size: CGSize(width: 300, height: 300)))
}
}
}

若想取得使用者的 email,記得呼叫 LoginManager 的 logIn 登入時參數 permissions 要包含 .email。

let manager = LoginManager()
manager.logIn(permissions: [.publicProfile, .email]) { result in
switch result {
case .success(granted: let granted, declined: let declined, token: let token):
print("success")
case .cancelled:
print("cancelled")
case .failed(_):
print("failed")
}
}

然後在登入成功後透過 GraphRequest 取得個人的相關資訊,比方以下例子:

let request = GraphRequest(graphPath: "me", parameters: ["fields": "id, email, name"])request.start { response, result, error in   if let result = result as? [String:String] {      print(result)   }}

結果

["name": "Deeplove Pan", "email": "deeplovepan@gmail.com", "id": "10214280447396741"]

FB 登出

let manager = LoginManager()manager.logOut()

讓全世界的 FB 帳號都能登入

剛剛我們順利實現 FB 登入功能,但其實只有我們自己的 FB 帳號能登入。如下圖所示,其它的 FB 帳號登入將出現錯誤訊息。

此問題有很多解法,其中一種是將別人的 FB 帳號加上測試權限。不過我們想要的是 App 上架後世界上每個人的 FB 帳號都能登入,因此我們進行以下的調整。

  • 設定 Privacy Policy URL & User data deletion

我們可先填入一個暫時的網址,方便測試 FB 的登入功能。等之後有正式的網址後,再回來修改。

進入 FB 開發網站上的 App 頁面,切換到 Settings > Basic 頁面,輸入 Privacy Policy URL & User data deletion 的網址。

進入 FB 開發網站上的 App 頁面,將右上的 Live 開關打開。

現在全世界的 FB 帳號都可以登入我們的 App 了 !

Facebook iOS SDK 內建的 FBLoginButton

若是懶得自己實現 FB 登入功能,也可以直接在畫面上加入 Facebook iOS SDK 內建的 FBLoginButton。

SwiftUI App

  • 將 FBLoginButton 包裝成 SwiftUI view LoginButton
import SwiftUI
import FacebookLogin
struct LoginButton: UIViewRepresentable {
func makeUIView(context: Context) -> FBLoginButton {
FBLoginButton()
}

func updateUIView(_ uiView: FBLoginButton, context: Context) {

}

typealias UIViewType = FBLoginButton


}
  • 在畫面上加入 LoginButton
import SwiftUIstruct ContentView: View {
var body: some View {

LoginButton()
.frame(width: 100, height: 40)

}
}

UIKit App

import UIKit
import FacebookLogin
class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

let loginButton = FBLoginButton()
loginButton.center = view.center
view.addSubview(loginButton)
}


}

在模擬器切換 FB 帳號

在模擬器登入後,它會記得我們登入的帳號,若將 App 移除再重新安裝,登入時一樣會採用之前的帳號。若想切換帳號,可以直接將模擬器重置後再測試。

作品集

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com