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/Twitter.h>
※なぜか自動補完が効かない(?)

Twitter.h

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

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 アプリは一通りできるのではないでしょうか?
AWS CLI と jq でインスタンス一覧を整形して表示
React と Laravel7 のプロジェクトを作成する
Homebrewインストール-2020年版
3直線で囲まれた範囲塗りつぶし
PuLP で線形最適化問題を解く
カスタムのペジネーションを作る
node-sass を使って sass をコンパイルする
Log ファサードでSQLログを分離して書き出す
いちから始める Docker - 複数のコンテナを使う - (2020年)
いちから始める Docker - docker-compose を使う - (2020年)
AWS ECR を使ってみる
Laravel7 でマルチ認証
Mac に AWS Client を設定する
Laravel 7 リリース
v-html でHTML表示する
Laravel で Vue コンポーネントを使う
Laravel で Nuxt.js を使ってみる(Docker環境)
いちから始める Docker -コンテナをビルド- (2020年)
いちから始める Docker -起動してみる- (2020年)
Mac で MySQL(8系)
composer で vendor がインストールできない
Eloquent の日付を Carbon で扱う
webpack 4 入門(npm編)
[Mac]容量を減らす
DIコンテナはじめ
freee SDKを Laravel で使ってみる
freee API を使ってみる
Segueを利用しない画面遷移
Xcode11.3 で XVim2 を利用する
Codable で JSONを読み込み
Webpack入門(yarn編)
MacからLaradock PostgreSQLで接続エラー
Dockerで不要なコンテナ・イメージを削除
Mac で Laradock の構築
yarn インストール&プロジェクト作成
Laravel 6.x 構築(Homestead編)
Composer インストール
nvm インストール
npm install が Mac でエラー
HTMLタグでカーソルが同時処理(ミラーリング)されてしまう
DI(依存性注入)
[Ubuntu]Let's Encryptで無料の証明書を利用する
[Apache]Apache2.4のアクセス制限が変更
[Ubuntu]rootのログインとsudo権限追加
タミヤ マイコンロボット工作セットをMacに接続してみた
pgAdimn4 でブラウザで開けなくなる
Java8 を HomebrewとjEnvで構築
Android Studio環境構築 2019
ロケールの再構築
vagrant グループに Apacheを追加