読者です 読者をやめる 読者になる 読者になる

SwiftでFacebook Page投稿しようとしたらハマった

以下、Facebook PageにPageとして投稿するというのをiOS8/Swiftで書いてみたものです。途中、投稿はできてもファンとしての投稿になってしまって、Pageのタイムラインにきっちり投稿できなかったのですがなんとかできました。実際に使うには戻りのチェックをもっとやって例外対応しないといけないでしょうけど。

func facebookShare(#appID: String, message: String) {
    // Accountsフレームワークを用いる。
    let accountStore = ACAccountStore()
    let facebookType = accountStore.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierFacebook)
    // あらかじめFacebookで開発者登録&アプリ登録する必要あり。ACFacebookAppIdKeyはそのアプリID。
    // ACFacebookAudienceKeyで投稿の公開範囲を設定できる。
    // ACFacebookPermissionsKeyにはpublish_actionsを入れる。publish_streamだとFB社の認証が必要らしい。
    let options = [ACFacebookAppIdKey: appID, ACFacebookAudienceKey: ACFacebookAudienceEveryone,
        ACFacebookPermissionsKey: ["publish_actions", "manage_pages"]]
    accountStore.requestAccessToAccountsWithType(facebookType, options: options) { (granted, e1) in
        if granted {
            let facebookAccounts = accountStore.accountsWithAccountType(facebookType)
            if facebookAccounts.count > 0 {
                // Facebookのアカウントは今のところiOSの仕様上、必ず一個だけ。
                let account = facebookAccounts[0] as ACAccount
                // Socialフレームワークを用いてPageのIDとトークンを取得する。
                var url = NSURL(string: "https://graph.facebook.com/v2.1/me/accounts")
                // ここはGETのAPI呼び出し
                var request = SLRequest(forServiceType: SLServiceTypeFacebook,
                        requestMethod: SLRequestMethod.GET, URL: url, parameters: nil)
                request.account = account
                request.performRequestWithHandler() { (responseData, httpResponse, e2) in
                    if responseData != nil {
                        // 戻りのJSONからPage IDとアクセストークンを取得する。
                        let ret = NSJSONSerialization.JSONObjectWithData(responseData,
                            options: NSJSONReadingOptions.AllowFragments, error: nil) as NSDictionary
                        let pageData = (ret.valueForKey("data") as NSArray)[0] as NSDictionary
                        let pageID = pageData.valueForKey("id") as String
                        let pageToken = pageData.valueForKey("access_token") as String
                        
                        // Pageのaccess_tokenに切り替える!切り替えることでPageとして投稿できる。
                        account.credential.oauthToken = pageToken
                        
                        url = NSURL(string: "https://graph.facebook.com/v2.1/\(pageID)/feed")
                        // 二回目のAPIコールは先とは変わってPOSTでの呼び出し
                        request = SLRequest(forServiceType: SLServiceTypeFacebook,
                                    requestMethod: SLRequestMethod.POST, URL: url, parameters: ["message": message])
                        request.account = account
                        request.performRequestWithHandler() { (responseData2, httpResponse, e3) in
                            dispatch_async(dispatch_get_main_queue()) { () in
                                self.logTextView.text = pageToken
                            }
                        }
                    }
                    if e2 != nil {
                        dispatch_async(dispatch_get_main_queue()) { () in
                            self.logTextView.text = e2.localizedDescription
                        }
                    }
                } // 2nd performRequestWithHandler
            } else {
                dispatch_async(dispatch_get_main_queue()) { () in
                    self.logTextView.text = ": アカウントが登録されていない?"
                }
            }
        } else {
                dispatch_async(dispatch_get_main_queue()) { () in
                    self.logTextView.text = ": 操作が許可されていない!"
                }
        }
    } // 1st performRequestWithHandler
}

ポイントは「account.credential.oauthToken = pageToken」でトークン切り替えるところ。一生懸命POSTパラメータで"access_token": pageTokenというのを付け加えていたけど、Socialフレームワークのクレデンシャルに、取得したPageのアクセストークンを直接ぶち込むことで切り替わるのであった。あとから考えると当然な作りなように思うけど、けっこう長時間ハマりました。

これはまださほどですが、Facebook API呼ぶ度にクロージャーが重なっていく、いわゆるコールバック地獄な宿命にiOSの実行スレッドを調整しなければならないために、サンプルコードならともかく実用に耐える作りってどうなんだろうと考えてしまいました。