NCMBでPodのuse_frameworks!有効時にFacebookSDKが入れられないことを解決した

  • 8
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

NCMBとFacebookSDK

NCMB

NCMBとは、ニフティクラウド mobile backendのことで、
ニフティクラウドが提供するmBaaSです。SNS連携もサポートされ、Facebookログインが用意されています。
http://mb.cloud.nifty.com/

FacebookSDK

iOSアプリでFacebookログインを使うには、通常はドキュメントにあるFacebookSDKが必要です。
http://mb.cloud.nifty.com/doc/current/sns/facebook_ios.html#Facebook SDKのインストール

use_frameworks!

CocoaPodsでは0.36からuse_frameworks!構文がサポートされました。
Swift 対応版 CocoaPods を使う - Qiita
http://qiita.com/taketin/items/8264aeebc5a626c6d48f
Swiftで書かれたライブラリをCocoaPods経由でインストールするには、このオプションが必須となりました。

問題

NCMBもFacebookSDKもそれぞれuse_frameworks!を指定してpod installまでは正常に入ります。
しかし、コンパイルすると以下のIssueにあるようにエラーが出てしまっていました。
https://github.com/NIFTYCloud-mbaas/UserCommunity/issues/299
Issueを見る限りずっと解決されていない問題のようでしたが、use_frameworks!を外すと非常に面倒なので、use_frameworks!がオンのままの方法を模索しました。

解決方法

ライブラリ化とサンプルアプリ

以下のレポジトリにライブラリ化し、サンプルアプリが動くようにしました
https://github.com/hiromi2424/NCMB-Facebook-use-framework

使い方

import NCMB_Facebook_use_framework

NCMB.setApplicationKey("APPキー", clientKey: "クライアントキー")
// ログインする
NCMBFacebookLogin(appId: "FacebookAppId").loginWithFacebook({ (user, account) in
    // ここでアカウント情報からユーザーに保存したい情報を設定する
    user.setObject(account.valueForKeyPath("properties.ACUIDisplayUsername"), forKey: "mailAddress")
}).then({ (user) -> Void in
    // userにFacebookログインで作成or取得されたNCMBUserが返ってくる
    self.user = user
}).always({
    // 成功・エラーに関わらず実行されるブロック
    self.loginButton.enabled = true
    self.refresh()
}).error({ (error) -> Void in
    // エラー表示はここでする
    self.alert(String(error))
})

サンプルアプリ実行

git clone https://github.com/hiromi2424/NCMB-Facebook-use-framework
cd NCMB-Facebook-use-framework/Example
pod install

上記実行後、XCodeでExample/NCMB-Facebook-use-framework.xcworkspaceを開きます。
スキーマをExampleに切り替えてビルドすることで動作を確認できます。
スクリーンショット 2016-04-30 14.31.25.png

※ pod tryで実行できるようにしたかったのですが、githubレポジトリを指定する方法はPrivate Specを用意する必要がありました。時間がある時にやってみます
http://stackoverflow.com/questions/25759170/how-to-add-a-private-cocoapod-as-a-dependency-in-another-pod-podspec-file

※ 前提の設定として、NCMBの管理画面で以下の設定が必要です
Facebook設定.png

Social Framework

SwiftでFacebook連携しよう! - Qiita iOS6で追加されたSocial.frameworkの使い方入門 - Qiita
http://qiita.com/happy_ryo/items/fef90627418b0a39a866

Social Frameworkを使い、Facebookの情報を取得します。

NCMBFacebookLogin.swift

    // FacebookアカウントをiOSのAccountsフレームワークを使って取得する
    // 取得できればACAccountのインスタンスをプロミス経由で返す
    // Facebookアカウントが複数ある場合は考慮していない(おそらくそのような状況は無い)
    public func getFacebookAccount() -> Promise<(ACAccount, ACAccountCredential)> {
        return Promise<(ACAccount, ACAccountCredential)>(resolvers: { (fulfill, reject) in
            let accountStore = ACAccountStore()
            let accountType = accountStore.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierFacebook)
            var options = [String: AnyObject]()
            options[ACFacebookAppIdKey] = appId
            options[ACFacebookPermissionsKey] = permissionsKey
            options[ACFacebookAudienceKey] = audienceKey

            accountStore.requestAccessToAccountsWithType(accountType, options: options, completion: { (granted, error) in
                if granted {
                    print("Success to get facebook account")
                    let accounts: NSArray = accountStore.accountsWithAccountType(accountType)
                    if accounts.count >= 1 {
                        let account: ACAccount = accounts.lastObject as! ACAccount
                        fulfill((account, account.credential))
                    } else {
                        reject(NSError(domain: "AccountNotFound", code: 403, userInfo: nil))
                    }
                } else {
                    if error.domain == "" {

                    }
                    print("facebook login error:")
                    print(error)
                    reject(error)
                }
            })
        })
    }

account.credentialを参照していないと、Promiseのブロックを抜けた時credentialがnilになる現象が発生してはまりました(GC関連だと思いますが、追いきれていません)

ここで取得した情報を元にFacebookログインすれば大丈夫そうに思えますが、SocialFrameworkで取得したFacebookIDだとNCMBにOAuth authentication errorと言われてしまいました。
デバッグの結果、Graph APIエクスプローラで取得したトークンを使ってIDを取得すると、SocialFrameworkで取得したFacebookIDと異なっていました。
https://developers.facebook.com/tools/explorer
アプリごとにFacebookIDが割り振られる仕様になったことが原因かと思います(間違っていたら教えて下さい)
以下のようにして直接GraphAPIを叩いて正しいほうのFacebookIDを取得します。

NCMBFacebookLogin.swift

    // AccountsフレームワークのFacebookIDが認証用に使えないことがあるので、GraphAPIを使ってグローバルに使えるIDを取得する
    public func getFacebookActualId(oauthToken: String) -> Promise<String> {
        let url = "https://graph.facebook.com/v2.6/me?access_token=\(oauthToken)"
        let req = Alamofire.request(.GET, url)
        return Promise<String>(resolvers: { (fulfill, reject) in
            req.responseSwiftyJSON({ (request, response, json, error) in
                if error == nil {
                    fulfill(json["id"].stringValue)
                } else {
                    reject(error!)
                }
            })
        })
    }

取得した情報を元にNCMBUserのFacebookログイン機能を使ってユーザーを取得することで、ログインすることができました。

まとめ

要点は2点です。

  • FacebookSDKが使えない時はSocialフレームワークを使う
  • NCMBのFacebookログインに渡すIDを取得する為にGraphAPIで正しいFacebookIDを取得する

use_frameworks!を公式で対応してくれないのは辛かったのですが、これでFacebookログインはなんとかなりそうです!