iOSの設定で認証済みのFacebookアカウントを用いて、Facebook APIから、タイムラインへ投稿をしたい場合があります。今回はその方法を説明します。

※ iOS 6で仕様が変わったので、それに準じて、SLRequest / SLComposeViewControllerを使う方法を説明します。

準備

以下のフレームワークをプロジェクトへ追加してください。

  • Accounts.framework
  • Social.framework

Facebook Developersでアプリ情報の登録をしておきます。

大事な部分は赤線を引いています。多分この通りにすれば動くはずです。(この画面の項目もちょくちょく変わるのですが・・・)

基本設定

setting01-1

バンドルIDは、アプリのBundle IDを入力してください。ストアIDは開発中はとりあえずは0で良いです。リリースしたら正式なものを入れたほうが良いでしょう。

詳細設定

setting02-1

これらをきちんと入力していないとFacebookアカウントにアクセスする権限を得られませんので注意してください。

iOSで認証済みのFacebookアカウントの取得とアクセス許可

まずはACAccountStoreオブジェクトを生成し、ViewControllerのメンバ変数などで保持するようにしてください。

_accountStore = [[ACAccountStore alloc] init];

次にFacebookのACAccountTypeオブジェクトを取得し、それを元にアカウントを取得します。

ACAccountType *accountType = [_accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSArray *accounts = [_accountStore accountsWithAccountType:accountType];
if (accounts.count == 0) {
   NSLog(@"Facebookアカウントが登録されていません");
   return;
}
  • ACAccountStore#accountTypeWithAccountTypeIdentifier – ACAccountTypeの取得
  • ACAccountStore#accountsWithAccountType – iOSの設定で登録されたアカウント(ACAccount)配列を得る

アカウント数が0ならば、Facebookアカウントが登録されてないということなので、これ以上の処理は不可能となります。この場合は、ユーザーにAlertで伝えるなどします。

アカウント情報を得ても、そのままではアカウント情報を使ってAPIを叩けるわけではありません。ユーザーの許可が必要になります。

NSDictionary *options = @{ ACFacebookAppIdKey : _STFacebookAppID,
                           ACFacebookAudienceKey : ACFacebookAudienceOnlyMe,
                           ACFacebookPermissionsKey : @[@"email"] };
[_accountStore requestAccessToAccountsWithType:accountType
  options:options
  completion:^(BOOL granted, NSError *error) {
     dispatch_async(dispatch_get_main_queue(), ^{
       if (granted) {
         [self authenticatePermissions];
       } else {
         NSLog(@"User denied to access facebook account.");
       }
     });
}];

ACAccountStore#requestAccessToAccountsWithTypeを呼ぶと、ユーザーがこのアプリに対して許可を与えていないなら、Alertが表示されます。

alert01

許可したかどうかは、completionブロック関数の引数grantedがYESかどうかで判定できます。1回ユーザーが許可した場合、2回目以降はAlertが表示されずにgranted == YESになります。

※ 最初に述べたアプリケーションの設定がきちんとなされていないと、ユーザーがAlertで許可したにもかかわらずgrantedがNOになります。

requestAccessToAccountsWithTypeの引数optionsは、いくつか注意が必要です。

  • ACFacebookAppIdKey – Facebook Developersのアプリ設定のApp IDを指定。
  • ACFacebookAudienceKey – 公開範囲を指定。CFacebookAudienceEveryone(全体)、ACFacebookAudienceFriends(友人のみ)、ACFacebookAudienceOnlyMe(非公開・自分のみ)
  • ACFacebookPermissionsKey – パーミッション

ややこしいのはパーミッションです。

こちらのページに記述されているパーミッションを配列で指定するのですが、最初にアカウント使用の許可を取る時は一部の基本的なパーミッションしか指定出来ません。上記のサンプルコードのように最初はemailを指定するのが無難なようです。

requestAccessToAccountsWithTypeのcompletionでgranted == YESだったときに、本来指定したいパーミッションを指定して、再びrequestAccessToAccountsWithTypeを呼ぶと良いと思います。サンプルコードでは、その処理をauthenticatePermissionsというメソッドで行なっています。

- (void)authenticatePermissions
{
    NSDictionary *options = @{ ACFacebookAppIdKey : _STFacebookAppID,
                               ACFacebookAudienceKey : ACFacebookAudienceOnlyMe,
                               // 本来指定したいpublish_streamパーミッションを指定。これで投稿可能になる。
                               ACFacebookPermissionsKey : @[@"publish_stream"] };
    ACAccountType *accountType = [_accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
    [_accountStore requestAccessToAccountsWithType:accountType
                                           options:options
                                        completion:^(BOOL granted, NSError *error) {
                                            dispatch_async(dispatch_get_main_queue(), ^{
                                                if (granted) {
                                                    //
                                                    // Get facebook account.
                                                    //
                                                    NSArray *accounts = [_accountStore accountsWithAccountType:accountType];
                                                    if (accounts.count > 0) {
                                                        _facebookAccount = [accounts objectAtIndex:0];
                                                    } else {
                                                        NSLog(@"Not found user.");
                                                    }
                                                } else {
                                                    NSLog(@"User denied to access facebook account.");
                                                }
                                            });
                                        }];
}

このrequestAccessToAccountsWithTypeの呼び出しで、場合によっては指定したパーミッションに対する確認Alertが表示されます。

alert03

このサンプルで指定しているpublish_stream(タイムラインへの投稿)ではAlertは表示されないかもしれません。上のAlertはfriends_birthdayを指定した時に表示されたものです。このAlertも1回だけ表示され2回目以降は表示なくcompletionが呼ばれます。

ここまでできたら、ACAccountStore#accountsWithAccountTypeを呼んでアカウントを取得しましょう。Facebookのアカウントは1つしか登録できないので、複数アカウントは考慮せずに、配列から最初のACAccountオブジェクトを取り出せば良いです。

_facebookAccount = [accounts objectAtIndex:0];

ユーザーのメールアドレスと名前、uidの取得

以下のように取得可能です。

// _facebookAccount(ACAccountオブジェクト)
NSString *email = _facebookAccount.username;
NSString *fullname = [[_facebookAccount valueForKey:@"properties"] objectForKey:@"fullname"];
NSString *uid = [[_facebookAccount valueForKey:@"properties"] objectForKey:@"uid"];

SLRequestを使って投稿

タイムラインへ投稿するには以下のようにします。

NSString *uid = [[_facebookAccount valueForKey:@"properties"] objectForKey:@"uid"];
NSString *urlString = [NSString stringWithFormat:@"https://graph.facebook.com/%@/feed", uid];
NSURL *url = [NSURL URLWithString:urlString];
NSDictionary *parameters = @{@"message" : [NSString stringWithFormat:@"test %llu", (uint64_t)[[NSDate date] timeIntervalSince1970]]};
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeFacebook
                                        requestMethod:SLRequestMethodPOST
                                                  URL:url
                                           parameters:parameters];
[request setAccount:_facebookAccount];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
  dispatch_async(dispatch_get_main_queue(), ^{
    NSUInteger statusCode = urlResponse.statusCode;
    if (200 <= statusCode && statusCode < 300) {
      NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
      NSLog([NSString stringWithFormat:@"id:%@", [response objectForKey:@"id"]]);
    } else {
      NSDictionary *fbErrorRoot = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
      NSString *fbErrorMessage = [[fbErrorRoot objectForKey:@"error"] objectForKey:@"message"];
      NSLog(fbErrorMessage);
    }
  });
}];
  • SLRequest#requestForServiceTypeでSLRequestオブジェクトを生成する。SLServiceTypeFacebookを指定
  • SLRequest#setAccountでアカウントを指定
  • SLRequest#performRequestWithHandlerでAPIにリクエストを投げる。ブロック関数で非同期にレスポンスが得られる。

performRequestWithHandlerのブロック関数はメインスレッドではないので、基本的にはdispatch_get_main_queue()を使うようにしてください。

SLComposeViewControllerによる投稿

投稿画面を表示してユーザーが入力した内容を投稿させたいときは、SLComposeViewControllerを使うと楽です。

fb02

見栄えも良いですね。たぶんiOS 7になればデザインもそれに準拠したものになるはずです。特に理由がない限りは無理して自分で作らないほうが良いでしょう。また公開範囲をリストから選択することも可能です。

if (![SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) return;
    
SLComposeViewController *con =
[SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
[con setCompletionHandler:^(SLComposeViewControllerResult result) {
  if (result == SLComposeViewControllerResultDone) {
    // 送信した時の処理
  } else if (result == SLComposeViewControllerResultCancelled) {
    // キャンセルした時の処理
  }
}];
[self presentViewController:con animated:YES completion:nil];
  • SLComposeViewController#isAvailableForServiceTypeで、Facebookへの投稿が可能かどうかを確認。
  • SLComposeViewController#composeViewControllerForServiceTypeで、投稿画面のViewControllerを生成。
  • SLComposeViewController#setCompletionHandlerで、Post(Done)、Cancelボタンを押した時の処理をする。

簡単ですね。

Post、Cancelを押した時、自動的にこのViewControllerは閉じられるので、特にdismissViewControllerを呼ぶ必要はありません。ちなみにTwitterの場合は、Cancelを2回押さないと閉じない問題があるのですが、Facebookの方は問題なく1回押せば閉じます。

このようにsetInitialTextを使うと初期入力状態の投稿内容を指定出来ます。

[con setInitialText:@"Checkin!"];

以上、Facebook APIに関する説明でした。
今回のサンプルコードはGitHubにコミットしてあります。
https://github.com/stack3/ACAccountSamples