※Firebaseにプロジェクトは作成済みとします。
下準備
まずはFacebookログインに関するDocumentや記事を読みながら、Facebookログインを実装するつもりで諸々の手続きを行います。
自分が知らないだけかもしれませんが、iOS用Facebookログイン - クイックスタートというFacebookの公式の記事は途中からObject-C言語での記述が出てきてチンプンカンプンになりました。
一連の手続きに関しては、iOSでFirebase AuthenticationをFacebookログインでやってみるという記事がとても参考になったので紹介しておきます。
簡単にですが手続きを書きます
- Facebook Developerに登録 //TwitterのDeveloper登録と異なり、あっけなく登録できました。
- アプリを作成 //Display NameはSetting>Basicで後から変更できます。
- Setting>BasicからApp IDとApp Secretを控えておく
- Firebase Authentication>ログイン方法にて、FacebookのAuthを有効にする //この際App IDとApp Secretの入力が必要になる。その入力画面の下に OAuth リダイレクト URI があり、それも控えておく必要がある
- Firebase Authentication>ログイン方法の、FacebookのAuthを有効にする画面下部の"OAuth リダイレクト URI"を控えておく
- Facebook Developers>Products>Facebook Login>Client OAuth Settings>Valid OAuth Redirect URIsに、先ほどの"OAuth リダイレクト URI"を記述する//最初のアプリの作成の仕方によっては、Productsがないかもしれません。その場合はダッシュボードからFacebook Loginを追加するっぽいです。
- FacebookSDKをpod installしていく //version4.39にはBugが残っているらしく、4.38以上4.39未満というversion指定をしました。
pod 'FBSDKLoginKit', '~> 4.38.0'
pod 'FBSDKCoreKit', '~> 4.38.0'
参考:Facebook Login Error - Unknown Error building URL (com.facebook.sdk.core error 3)
なお、Firebase Authは既にインストールしているものとします。
- info.plistの編集 //ところどころ、控えていたApp IDを記述する必要があります。記述しないとうまく動作しません。
- AppDelegateの編集
//公式のFacebook Documentではなく、iOSでFirebase AuthenticationをFacebookログインでやってみるのAppDelegateが参考になります。
FirestoreとFacebookを連携させる
シンプルにFacebookにログインさせたい場合は、たくさん記事がWeb上に転がっているので調べてみてください。
自分の場合は、Firebase Authenticationにて、匿名ログインを許可に設定し、
デフォルトの状態では匿名ログインさせておいて、希望者のみFacebookと連携させたいという方針がありました。
FBSDKLoginButton()を使ったり、signInAndRetrieveData(with: credential)を最初試してみたものの、イマイチ自分の目指すような挙動をすることはありませんでした。
//FBSDKLoginButton()には、よくあるFacebookログインボタンが初めから用意されていて、デザインの変更などが効かない
//signInAndRetrieveData(with: credential)はログイン・ログアウトのタイミングで全く新しいuidが生成されてしまって、データが引き継げなかった。
そこで使用したのが、
FBSDKLoginManager()と、linkAndRetrieveDataです。
FBSDKLoginManager()ではログイン処理を実装できます。ユーザー情報を取得するに当たって重要なpermissionも記述できます。
linkAndRetrieveDataはFirebaseの公式Document:iOS で Firebase 匿名認証を行うに書かれています。
import UIKit
import Firebase
import FirebaseFirestore
import FirebaseAuth
import FBSDKCoreKit
import FBSDKLoginKit
上記は追加するモジュールです。
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let loginManager = FBSDKLoginManager()
if let user = Auth.auth().currentUser {
if user.providerData.isEmpty {
loginManager.logIn(withReadPermissions: ["public_profile","email"], from: self) { (result, error) in
if (error != nil) {
print("permission Errorメッセージ:\(error!)")
}
if let token = FBSDKAccessToken.current() {
let credential = FacebookAuthProvider.credential(withAccessToken: token.tokenString)
user.linkAndRetrieveData(with: credential, completion: { (authResult, error) in
if error != nil {
print("ログイン Errorメッセージ:\(error!)")
return
}
self.tableView.reloadData() //tableView更新して表示を変える
self.fetchUserProfile() //ユーザーデータを取ってくる
})
}
}
return
} else {
for item in user.providerData {
if item.providerID == "facebook.com" {
//ログアウト処理です。unlinkだけでは完全にログアウトしたことにはならないようなので要注意です。
loginManager.logOut()
user.unlink(fromProvider: "facebook.com") { (user, error) in
self.tableView.reloadData()
return
}
} else {
return
//Twitterとかとは連携しているが、Facebookとは連携していないケースに、Facebookとの連携を行う操作(省略)
}
}
}
} else {
print("Auth.auth().currentUserはnilです")
}
}
userの型はFIRUserでプロパティとしてproviderDataを持っています。Facebookへログインすると、providerDataのプロパティであるproviderIDに、"facebook.com"が追加されます。
ですので、上のコードでは、providerDataの有無で場合を分けて、Facebookにログインしていない場合はログイン処理をするというコードになります。
loginManager.logIn(withReadPermissions: ["public_profile","email"], from: self) { (result, error) in
}
この部分がログイン処理になります。permissionsは、["public_profile","email"]で大丈夫です。
["picture","name"]などと書いてもエラーになります。なぜなら"public_profile"の中に、defaultで引っ張ってこれるユーザーデータは含まれているからです。詳しくはFacebookのdocument参照です。
Profile情報をfetchする
fetchUserProfile()の中身を書いていきます。
func fetchUserProfile(){
let graphPath = "me"
let parameters = ["fields": "id, name, picture"]
let graphRequest = FBSDKGraphRequest(graphPath: graphPath, parameters: parameters)
var userInfo:[String : AnyObject] = [:]
graphRequest?.start { (connection, result, error) in
if let error = error {
print(error.localizedDescription)
} else {
userInfo = result as! [String : AnyObject]
if let user = Auth.auth().currentUser {
let userRef = Firestore.firestore().collection("users").document("\(user.uid)")
userRef.updateData(userInfo){ err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
}
}
Facebookにログインした後は、Graph APIを使ってデータを取得していきます。
こちらの記事:How to use Graph API to retrieve Facebook user information in iOSが参考になりました。
//Graph APIを初期化
let graphPath = "me"
let parameters = ["fields": "id, name, picture"]
let graphRequest = FBSDKGraphRequest(graphPath: graphPath, parameters: parameters)
graphRequest?.start { (connection, result, error) in
if let error = error {
print(error.localizedDescription)
} else {
print(result)
}
}
Firestoreへアップロードするために、細工をしていきます。
まずは、userInfoというDictionaryを用意し、resultをキャストして入れます。
var userInfo:[String : AnyObject] = [:]
let graphPath = "me"
let parameters = ["fields": "id, name, picture"]
let graphRequest = FBSDKGraphRequest(graphPath: graphPath, parameters: parameters)
graphRequest?.start { (connection, result, error) in
if let error = error {
print(error.localizedDescription)
} else {
userInfo = result as! [String : AnyObject]
}
}
そして、graphRequest?.startのcallback関数の中に、データをFirestoreへアップロードする処理を記述していきます。
graphRequest?.start { (connection, result, error) in
if let error = error {
print(error.localizedDescription)
} else {
userInfo = result as! [String : AnyObject]
if let user = Auth.auth().currentUser {
let userRef = Firestore.firestore().collection("users").document("\(user.uid)")
userRef.updateData(userInfo){ err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
}
graphRequest?.startのcallback関数の外に、updateData(userInfo)を記述していたら、userInfoの中身が認識されないのかアップロードできませんでした。callback関数の中に記述すれば無事に更新できます。
Firestoreに保存したpictureを引っ張ってくる
idやnameを使う際には特に苦戦しなかったのですが、pictureを扱う際にFirestore独特のmap(マップ)というデータ型の扱いに少し苦戦したので、そちらも書いておきます。
なお、ここでいうmap(マップ)は、メソッドとしてのmapではありません。
上記画像はFirebase Firestoreのマップ型のデータです。
func fetchProfileData() {
if let user = Auth.auth().currentUser{
let ref = Firestore.firestore().collection("users").document("\(user.uid)")
ref.getDocument { (document, error) in
if let document = document, document.exists {
//こちらは[String:AnyObject]の辞書型なので、["name"]でkeyを指定してあげれば、valueを取り出せる。その際にString型にダウンキャストする。
if let nameLabelText = document.data()!["name"] as? String {
self.nameLabel.text = nameLabelText
}
//pictureの方は階層構造になっていて、一つ一つキャスティングして掘っていく必要があります。そのままではAnyObject型なので、document.data()!["picture"]["data"]["url"]という風に指定しようとするとエラーが起きます。
if let pictureData = document.data()!["picture"] as? [String : Any] {
let pictureProperties = pictureData["data"]! as! [String:Any]
let imageUrl = pictureProperties["url"] as! String
self.profileImage.sd_setImage(with: URL(string: imageUrl), placeholderImage: UIImage(named: "profile"))
}
} else {
print("Document does not exist")
}
}
}
}
エラー処理の書き方はもっと良い方法があるかもしれませんが、とりあえずオプショナルバインディングでこなしました。
(追記)Facebookアプリを公開する
以上で連携はできるのですが、そのままではApp Storeに申請してもリジェクトされます。
なぜなら、連携させているFacebookアプリが開発者モードで公開されていないからです。
β版アプリをテストユーザーに触ってもらったり、アプリを申請する段階になったら、FacebookのDeveloperページにて、まずプライバシーポリシーの書かれたURLを記入した上で、画面上部のステータスを「開発モード」から「公開モード」変更します。
そうすれば、自分以外のユーザーも連携することができるようになります。