【Swift5】Facebookログインしてプロフィール情報を取得する

#環境

- Xcode10.2.1
- Swift5.0

#やること

アプリからFacebookログインして、プロフィール情報を取得します。
今回取得する情報は、ユーザー名、ユーザーアイコン、メールアドレスです。

![Facebooklogin.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/6ce9ee49-cc31-d232-77bf-7e0ffa68ec57.gif)

# 事前情報
## 取得できる情報
- フルネーム
- 苗字
- 名前
- プロフィール画像
- メールアドレス
- 友達リスト
etc...

## Facebook側に申請すれば取得できる情報・操作
- 友達リスト
- 利用者がアプリからFacebookグループにコンテンツを公開するのを許可
- 利用者の年齢層
- 誕生日
- 利用者が主催するか出欠確認の回答をしたイベントに読み取り専用アクセスできる許可をアプリに付与
- 性別
- 出身地
- 利用者が「いいね！」したすべてのFacebookページとOpen Graphオブジェクトのリストにアクセス
- 写真にアクセス

※詳細は以下を参照
https://developers.facebook.com/docs/facebook-login/permissions#reference-default

## 実装するまでに必要な情報・準備

- バンドルID(Facebookに登録するために必要(実際に何に使われているのかは未確認)
ソース：https://developers.facebook.com/docs/facebook-login/ios
- Facebookのダッシュボードにアプリの登録が必要(アプリ側に登録するアプリID取得のため)(この記事ではその手順も記載しています。)
- プライバシーポリシーのURL
- developerサイトに登録するアプリアイコン(1024 x 1024)

# Facebook Developersでアプリ登録から実装までの手順
## ①Facebook Developersにログイン

https://developers.facebook.com/ からFacebookログイン。
（Facebookアカウントのない人は作成が必要です。）

<img width="1055" alt="スクリーンショット 2019-07-02 9.01.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/16e99fc0-7e52-bad6-2f72-91741503949e.png">

ログインすると、Facebook Developersの画面に遷移します。

<img width="1271" alt="スクリーンショット 2019-07-02 9.02.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/1d7057fc-ff53-2054-d4f1-3abe36114ef7.png">

画面左上にある、スタートガイドを選択してください。

<img width="414" alt="スクリーンショット 2019-07-02 9.05.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/ffdc7385-e52d-2610-d91c-cd524c5f6685.png">

<img width="661" alt="スクリーンショット 2019-07-02 9.06.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/77cae76d-3e99-2e15-c3dc-ffaa96992dad.png">


「次へ」ボタンを押した後、手順に沿って以下の中からいちばんよく当てはまるものを選択します。

<img width="746" alt="スクリーンショット 2019-07-02 9.09.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/fda7ff21-9cad-a45a-b17b-b8681284027f.png">

これでアカウント登録は完了です。
<img width="751" alt="スクリーンショット 2019-07-02 9.10.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/73131785-b8a0-b7fa-6f15-9f29d85e496e.png">

## ②アプリ登録
アカウント登録が完了したら、アプリの登録を行います。

<img width="284" alt="スクリーンショット 2019-07-17 8.43.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/43151063-6d05-0859-c1ea-ff64fbfca832.png">

なんと、初回登録画面からは、アプリ名しか入力しません！

<img width="716" alt="スクリーンショット 2019-07-02 9.13.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/c43caad8-4585-23d8-8b84-031f913b49d0.png">

登録が成功すると、以下のキャプチャのようにアプリIDが発行されます。

<img width="1279" alt="スクリーンショット 2019-07-17 8.42.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/a4d4f60c-776b-02b0-b984-285b7a413a1e.png">

## ③ログイン設定

続いて、プロダクトのログイン設定を行います。
画面左側のプロダクトを選択します。

<img width="1279" alt="スクリーンショット 2019-07-17 8.42.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/09f51aca-c2f7-6051-b80f-f1b395b82e9f.png">

Facebookログインを選択します。
<img width="1239" alt="スクリーンショット 2019-07-17 8.57.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/1a15c404-80bf-cdf4-07c3-762aa3439083.png">

今回は、iOSアプリでログインをするので、一番左側の「iOS」を選択します。

<img width="619" alt="スクリーンショット 2019-07-23 19.56.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/1ee074fb-b031-039d-a938-afaf6668b8df.png">


## ④Xcodeでビルド設定

手順に従って、Facebookログインを使用する設定を行います。

```
pod 'FBSDKLoginKit'
```

info.plistに以下の内容を追加します。

```
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLSchemes</key>
            <array> <string>fb1234567890</string></array>
        </dict>
    </array>
    <key>FacebookAppID</key>
    <string>1234567890</string>
    <key>FacebookDisplayName</key>
    <string>テストアプリ</string>
```

続いて、手順に従って以下も追記します。

```
    <key>LSApplicationQueriesSchemes</key>
    <array> <string>fbapi</string>
        <string>fb-messenger-share-api</string>
        <string>fbauth2</string>
        <string>fbshareextension</string>
    </array>
```

テキスト編集を終えてリストで見ると、plistはこんな感じになってます。
<img width="508" alt="スクリーンショット 2019-07-23 19.35.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/7c90463a-4c75-f56c-213f-12c9f30ccf99.png">

フラグ-ObjCを[Other Linker Flags]に追加します。

<img width="726" alt="スクリーンショット 2019-07-21 18.09.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/d55e2f82-29df-ae97-7b12-faaeccd1c6e8.png">

## ⑤実装

ビルド設定が終わったら、実装をします。

AppDelegateに以下のコードを追加します。

```.swift

import UIKit
import FBSDKCoreKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    
    func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    func application(_ application: UIApplication,open url: URL,sourceApplication: String?,annotation: Any) -> Bool {
        return ApplicationDelegate.shared.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation)
    }
    
    func applicationDidBecomeActive(_ application: UIApplication) {
        AppEvents.activateApp()
    }
    
}
```

実装内容は以下のような感じになりました。

```ViewController.swift
import UIKit
import FBSDKLoginKit

class ViewController: UIViewController, LoginButtonDelegate {
    
    // ユーザープロフィール
    var userProfile : NSDictionary = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Facebookログイン用ボタンがSDKに用意されている
        let facebookLoginButton = FBLoginButton()
        // アクセス許可
        facebookLoginButton.permissions = ["public_profile", "email"]
        facebookLoginButton.center = self.view.center
        facebookLoginButton.delegate = self
        self.view.addSubview(facebookLoginButton)
        
        // ログイン済みかチェック
        checkloginFacebook()
    }
    
    /// ログインコールバック
    func loginButton(_ loginButton: FBLoginButton, didCompleteWith result: LoginManagerLoginResult?, error: Error?) {
        // エラーチェック
        if error == nil {
            // ログインがユーザーにキャンセルされたかどうか
            if result!.isCancelled {
                print("Login　Cancel")
                
            } else {
                // ユーザープロフィールを保存
                self.saveUserData()
            }
        } else {
            print("Login　Error")
        }
    }
    
    /// ログアウトコールバック
    func loginButtonDidLogOut(_ loginButton: FBLoginButton) {
        print("Logout")
    }
    
    /// ログイン済みかチェック
    func checkloginFacebook() {
        if let _ = AccessToken.current {
            print("Logged in")
            
        } else {
            print("Not Logged in")
        }
    }
    
    // 参考：SwiftでFacebookログインと情報取得の覚書
    // https://qiita.com/noranoko/items/e1406cdd957f439db066
    /// ユーザー情報を取得
    func saveUserData() {
        
        let graphRequest : GraphRequest =
            GraphRequest(graphPath: "me",
                         parameters: ["fields": "id, name, first_name, last_name, picture.type(large), email"])
        
        graphRequest.start(completionHandler: { (connection, result, error) -> Void in
            if ((error) != nil) {
                print("Error: \(String(describing: error))")
            } else {
                // プロフィール情報をディクショナリに入れる
                self.userProfile = (result as! NSDictionary)
                
                // 名前
                let name: String = self.userProfile.object(forKey: "name") as? String ?? ""
                // メールアドレス
                let mailAddress: String = self.userProfile.object(forKey: "email") as? String ?? ""
                // アイコン画像
                let picture: NSDictionary = self.userProfile.object(forKey: "picture") as! NSDictionary
                let data: NSDictionary = picture.object(forKey: "data") as! NSDictionary
                let pictureUrl: String = data.object(forKey: "url") as! String
                
                // UserDefaultsに保存
                UserDefaults.standard.set(name, forKey: Const.UserDefaults.kUserNameKey)
                UserDefaults.standard.set(mailAddress, forKey: Const.UserDefaults.kMailAddressKey)
                UserDefaults.standard.set(pictureUrl, forKey: Const.UserDefaults.kPictureKey)
                
                // 画面遷移
                self.transitionSecondView()
            }
        })
    }
    
    /// 画面遷移
    func transitionSecondView() {
        self.performSegue(withIdentifier: "segue", sender: nil)
    }
}
```
プロフィール情報は、UserDefaultsに保存しています。

UserDefaultsのキーはConstに定義しています。

```.swift
struct Const {
    struct UserDefaults {
        /// ユーザー名
        static let kUserNameKey: String =  "UserNameKey"
        /// メールアドレス
        static let kMailAddressKey: String = "MailAddressKey"
        /// アイコン画像
        static let kPictureKey: String =  "PictureKey"
    }
}
```
※ソースコードはツッコミどころばかりだと思うので、あまり参考にしないでください(とりあえず動くレベルです。)

```SecondViewController.swift
import  UIKit

final class SecondViewController: UIViewController {
    
    /// アイコン画像
    @IBOutlet weak var userImageView: UIImageView! {
        didSet {
            if let pictureUrl = UserDefaults.standard.object(forKey: Const.UserDefaults.kPictureKey) {
                do {
                    let pictureUrlString = pictureUrl as! String
                    if let url = URL(string: pictureUrlString) {
                        let data = try Data(contentsOf: url)
                        let image = UIImage(data: data)
                        self.userImageView.image = image
                    }
                } catch let err {
                    print("Error : \(err.localizedDescription)")
                }
            }
        }
    }
    
    /// ユーザー名
    @IBOutlet weak var nameLabel: UILabel! {
        didSet {
            self.nameLabel.text = (UserDefaults.standard.object(forKey: Const.UserDefaults.kUserNameKey)) as? String
        }
    }
    
    /// メールアドレス
    @IBOutlet weak var mailAddressLabel: UILabel! {
        didSet {
            self.mailAddressLabel.text = (UserDefaults.standard.object(forKey: Const.UserDefaults.kMailAddressKey)) as? String
        }
    }
}

```
ログインしてプロフィール情報(トークン)を取得したら画面遷移して表示するといった流れです。
ここまで実装が完了したら、早速アプリを起動してみます。

## ⑥ログインしてみる

アプリを起動してみます。
起動すると、Facebookのログイン画面が起動されるので、ユーザー情報を入力してログインします。

![Simulator Screen Shot - iPhone Xʀ - 2019-07-21 at 21.25.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/709c610c-1407-5190-ccb3-914dbab211ec.png)

しかし、「このアプリは開発者モードになっているため、アクセスできません。」と表示されてしまいます。
そこで、Facebookの設定で、開発者モードをライブモードに変更します。

## ⑦ライブモード設定に変更する

設定は以下のスイッチで変更できます。
<img width="1200" alt="スクリーンショット 2019-07-21 21.25.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/69061772-ecb9-ffac-a2fa-6bf2ba3494ca.png">

プライバシーポリシーのURLを設定していないと、以下のようなエラーが表示されます。
Facebookログインするには、プライバシーポリシーのURLの設定をするしかありません。
※今回は、仮でhttps://www.google.com/ を設定しました。

設定が完了したら、もう一度開発者モードを変更してみましょう。
カテゴリを選択して「承認」ボタンを押下します。
<img width="470" alt="スクリーンショット 2019-07-21 21.43.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/118f3d14-77e5-12fa-d0ef-3d9273ba7b70.png">

<img width="215" alt="スクリーンショット 2019-07-21 21.44.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/9fcfda55-819a-9b84-9f4f-0d893663113e.png">

ステータスが、「ライブ」になると、ログインができるようになります。

アプリ上で、一度でもFacebookにログインすると、

<img width="222" alt="スクリーンショット 2019-07-23 9.15.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/5aeeaa1e-abdc-d130-f793-e01ccd0a9cf2.png">

Facebookログインの左側のチェックマークが緑色になります。


#まとめ

![Facebooklogin.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/6ce9ee49-cc31-d232-77bf-7e0ffa68ec57.gif)

以上が、Facebookのログインの大まかな手順になります。
Cognitoなど別のサービスが関わってくると、実装内容やSDKの使用の有無も変わってきますので、注意が必要です。
FacebookSDK自体は比較的使いやすい印象でした♪
ご指摘事項などありましたら、コメントいただけると幸いです。

#参考

- [Swift4.0で「Facebookでログイン」実装するまでを書きたい](https://qiita.com/palinco41kcal/items/92b64eadc54a46350d6c)
- [SwiftでFacebookログインと情報取得の覚書](https://qiita.com/noranoko/items/e1406cdd957f439db066)
- [iOS用Facebookログイン - クイックスタート(公式)](https://developers.facebook.com/docs/facebook-login/ios)