iOS5 で簡単に Twitter を利用する

2011/12/22

iOS5 から Twitter が標準で利用できるようになったので使ってみた。

ちなみに、実装アプリとしてうちの会社でリリースした六曜が検索できるアプリ「Rokuyou」の占い投稿で利用してます(^_^)

Twitter Developer でドキュメント

Twitter API Content tagged with iOS5 が結構充実しているかと思います。

Twitter.framework

Twitter を利用するには、Twitter.framework を追加し、ソースに Twitter.h をimport が必要。 twitter

#import 

※なぜか自動補完が効かない(?)

Twitter.h

ちなみに、Twitter.h は TWRequest.h と TWTweetComposeViewController.h を import します。

#import 
#import 

TWTweetComposeViewController

メールと同様に、Tweet も予め用意されたUI画面から投稿します。 tweet

アカウントはiOS5 標準のTwitterアカウント設定を利用(正式には ACAccount)するので、初めて利用する場合は、TWTweetComposeViewController が生成されると自動的にナビゲートされます。 アプリ独自にアカウントを保存する実装が要らないので凄く便利。

利用できるメソッドもそれほど多くないです。

enum TWTweetComposeViewControllerResult {
    TWTweetComposeViewControllerResultCancelled,
    TWTweetComposeViewControllerResultDone
};
typedef enum TWTweetComposeViewControllerResult TWTweetComposeViewControllerResult;   
typedef void (^TWTweetComposeViewControllerCompletionHandler)(TWTweetComposeViewControllerResult result); 

UIKIT_CLASS_AVAILABLE(5_0) @interface TWTweetComposeViewController : UIViewController {
}

//Tweet が可能かどうか?
+ (BOOL)canSendTweet;

//初期のメッセージ
- (BOOL)setInitialText:(NSString *)text;

//添付画像追加
- (BOOL)addImage:(UIImage *)image;

//添付画像を全て削除
- (BOOL)removeAllImages;

//URLを追加
- (BOOL)addURL:(NSURL *)url;

//全てのURLを削除
- (BOOL)removeAllURLs;

//Tweet 完了後のハンドラー
@property (nonatomic, copy) TWTweetComposeViewControllerCompletionHandler completionHandler;
- (void)openTweet
{
    UIImage *image = [self captureScreen];
    
    TWTweetComposeViewController * composeViewController = [[TWTweetComposeViewController alloc] init];
    [composeViewController setInitialText:[self createComment]];
    [composeViewController addImage:image];
    [self presentModalViewController:composeViewController animated:YES];
}

completionHandlerを利用する

Tweet を投稿、キャンセル後のハンドラーを取得する事ができますが、completionHandler プロパティにblock文を記述します。 処理の種類はTWTweetComposeViewControllerResult 型で判別します。

    TWTweetComposeViewController * composeViewController = [[TWTweetComposeViewController alloc] init];
    [composeViewController setInitialText:@"デフォルトコメント"];
    
    composeViewController.completionHandler = 
    ^(TWTweetComposeViewControllerResult result) {
        switch (result) {
            case TWTweetComposeViewControllerResultDone:
                NSLog(@"TWTweetComposeViewControllerResultDone");
                break;
            case TWTweetComposeViewControllerResultCancelled:
                NSLog(@"TWTweetComposeViewControllerResultCancelled");
                break;
            default:
                NSLog(@"other");
                break;
        }
        [self dismissModalViewControllerAnimated:YES];
    };

この際、dismissModalViewControllerAnimated: などで終了しておかないと、思わぬ挙動となるので注意が必要です。

TWRequest.h

TWTweetComposeViewController 予め用意されているコンポーネントですが、 Twitter API を直接利用するには TWRequest を使って、URL、パラメータを指定してリクエストします。

Use TWrequest to send an image with a text to Twitter in IOS5 を参考に画像とテキストを同時に投稿します。

まず、Twitter のメディア投稿 API URLは、Twitter Developer - POST statuses/update_with_media を参照

例えば、画像付きテキストの投稿 URL は

https://upload.twitter.com/1/statuses/update_with_media.json

TWRequest インスタンス生成時に NSURL、TWRequestMethod を指定します。 ※パラメータ(NSDictionary) は任意

TWRequestMethod は以下の3種類

    TWRequestMethodGET,
    TWRequestMethodPOST,
    TWRequestMethodDELETE

データ追加は 各オブジェクト をNSData 変換して addMultiPartData で TWRequest に追加します。

そして最後の送信処理は、performRequestWithHandler でリクエストしますが、これが少々癖があります。 非同期処理なので、Twitter Developer では block 文で記述するのが推奨(と言うかこれしかできない?)されています。

アカウント取得

アカウントを取得したい場合は、ACAccountStore で取得可能ですが、複数アカウントが設定できるため、返り値は NSArray です。 ACAccountStore を利用するには、Accounts.framework の読み込みが必要です。

- (NSArray *)fetchAccounts
{
    ACAccountStore *accountStore = [[ACAccountStore alloc] init];
    ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
    
    NSArray *accounts = [NSArray array];
    [accountStore requestAccessToAccountsWithType:accountType withCompletionHandler:
     ^(BOOL granted, NSError *error) {
        if (granted) {
            _accounts = [accountStore accountsWithAccountType:accountType];
        }
    }];
    return accounts;
}

これも気になるのが、withCompletionHandler でしょうか? アカウント取得後の処理も非同期処理なので、それを考慮しないといけません。

ACAccount

ACAccountStore で利用できるプロパティは、以下の通り。

- (id)initWithAccountType:(ACAccountType *)type;
@property (nonatomic, readonly) NSString            *identifier;
@property (nonatomic, retain)   ACAccountType       *accountType;
@property (nonatomic, copy)     NSString            *accountDescription;
@property (nonatomic, copy)     NSString            *username;
@property (nonatomic, retain)   ACAccountCredential *credential;

Identifier は読み込み専用。 ACAccountType の各プロパティは読み込み専用の為、事実上 ACAccountTypeIdentifierTwitter しか利用できないかと思います。 ACAccountCredential は、現時点で OAuthToken の情報を保持します。

以上を踏まえて、投稿とタイムラインの取得をしてみます。 アカウントは、最初のアカウントを取得しているのでちょっと手抜きです。

テキストと画像を投稿

- (void)requestTweet:(NSString *)message :(UIImage *)image
{
    ACAccountStore *store = [[ACAccountStore alloc] init];
    ACAccountType *twitterAccountType = [store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
    
    [store requestAccessToAccountsWithType:twitterAccountType 
                     withCompletionHandler:^(BOOL granted, NSError *error) {
                         if (!granted) {

                         } else {
                             NSArray *twitterAccounts = [store accountsWithAccountType:twitterAccountType];
                             
                             if ([twitterAccounts count] > 0) {
                                 NSURL *url = 
                                 [NSURL URLWithString:
                                  @"https://upload.twitter.com/1/statuses/update_with_media.json"];
                                 
                                 TWRequest *request = [[TWRequest alloc] initWithURL:url parameters:nil 
                                                  requestMethod:TWRequestMethodPOST];
                                 [request setAccount:[twitterAccounts objectAtIndex:0]];
                                 
                                 if (image != nil) {
                                     NSData *imageData = UIImagePNGRepresentation(image);
                                     [request addMultiPartData:imageData 
                                                      withName:@"media[]" type:@"multipart/form-data"];
                                 }

                                 NSString *status = message;
                                 [request addMultiPartData:[status dataUsingEncoding:NSUTF8StringEncoding] 
                                                  withName:@"status" type:@"multipart/form-data"];
                                 
                                 [request performRequestWithHandler:
                                  ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
                                      NSDictionary *dict = 
                                      (NSDictionary *)[NSJSONSerialization 
                                                       JSONObjectWithData:responseData options:0 error:nil];
                                      
                                      NSLog(@"%@", dict);
                                      
                                      dispatch_async(dispatch_get_main_queue(), ^{

                                      });
                                  }];
                             }
                         }
                     }];
    

}

Home Timeline を取得


ACAccountStore *store = [[ACAccountStore alloc] init];
    ACAccountType *twitterAccountType = [store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

    [store requestAccessToAccountsWithType:twitterAccountType 
                     withCompletionHandler:^(BOOL granted, NSError *error) {
                         if (!granted) {
                             
                         } else {
                             NSArray *twitterAccounts = [store accountsWithAccountType:twitterAccountType];
                             
                             if ([twitterAccounts count] > 0) {
                                 ACAccount *account = [twitterAccounts objectAtIndex:0];

                                 NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
                                 [params setObject:@"1" forKey:@"include_entities"];

                                 NSURL *url = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/home_timeline.json"];
                                 
                                 TWRequest *request = 
                                 [[TWRequest alloc] 
                                  initWithURL:url parameters:params requestMethod:TWRequestMethodGET];
                                 
                                 [request setAccount:account];
                                 [request performRequestWithHandler:
                                  ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
                                      if (error != nil) {
                                          NSLog(@"%@", error);
                                      } else {
                                          NSError *jsonError;
                                          NSArray *timeline = 
                                          [NSJSONSerialization 
                                           JSONObjectWithData:responseData 
                                           options:NSJSONReadingMutableLeaves 
                                           error:&jsonError];            
                                          if (jsonError == nil) {                          
                                              NSLog(@"%@", timeline);
                                          } else { 

                                              NSLog(@"%@", jsonError);
                                          }
                                      }
                                  }];
                             }
                         }
                     }];

これで Twitter アプリは一通りできるのではないでしょうか?