のんびりしているエンジニアの日記

ソフトウェアなどのエンジニア的な何かを書きます。

iPhone Application Twitterクライアントを作ろう

Sponsored Links

皆さんこんにちわ。
お元気ですか。私は眠いです。

今日はiPhone ApplicationでTwitterクライアントを作ってみようと思います。
Xcodeのインストールは割愛します。

プロジェクトの作成

まずは、新しいプロジェクトを生成します。今回はSingleViewApplicationを選択

f:id:tereka:20140225083301p:plain

ProjectNameに適当な内容を追加、今回はTwitterApplicationとしました。

f:id:tereka:20140225093358p:plain

適当なフォルダでCreateを実行

f:id:tereka:20140225083319p:plain

StoryBoard

画面を作成

次にstoryboardで画面を構築します。Main_iPhone.storyboardを選択。
最初は以下のようになっていると思います。

f:id:tereka:20140225083701p:plain

早速ViewControllerを削除し、TableViewを追加

f:id:tereka:20140225085621p:plain

Table View Controllerを選択して、タブからEditor→Embed in →Navigation Controllerを選択

f:id:tereka:20140225084943p:plain

投稿ボタンを追加

次に投稿ボタンを作ってみましょう。まずは、ボタンを置く。

f:id:tereka:20140225085800p:plain

そしてプロパティにて変更する。identifierをComposeに

f:id:tereka:20140225090003p:plain

ファイルとViewの紐付け

新しくファイルを作ります。このファイルに先ほど生成したTable Viewの実装を記入します。
Objective-C classを選択し、名前は適当に作ります。今回はTwitterViewControllerとしました。

f:id:tereka:20140225090426p:plain

しかし、現在、TableViewとこのTwitterViewControllerが紐づいていません。
紐づけましょう。Main_iPhone.storyboardを開き、TableViewをクリック
Custom Classのタブを選択し、TwitterViewControllerを選択

f:id:tereka:20140225090857p:plain

これで紐付けができました。

投稿ボタン実装編

ボタンはまだ紐づいていません。まずは、ボタンを押した時に実行するメソッドを登録しましょう。
TwitterViewController.hを開きましょう。

その上で、ボタンをクリックし、コントロールを押しながらファイルに線をのばすと画面が表れます。
ConnectionはActionを選択し、名前をTwitterButtonとしました。

f:id:tereka:20140225093610p:plain

するとコードも変化し、以下のようになっていると思います。

#import <UIKit/UIKit.h>

@interface TwitterViewController : UITableViewController
- (IBAction)TwitterButton:(id)sender;

@end

これでメソッドを登録することができました。
さて、ボタンのメソッドの本体を書きましょう。

今度はTwitterViewController.mを開きます。
最後の方に

- (IBAction)TwitterButton:(id)sender {
}

が追加されているので、この中に書きます。

ヘッダーにを追加して

以下のように変更してください。

#import <Social/Social.h>

(略)

- (IBAction)TwitterButton:(id)sender {
    SLComposeViewController *twitterPostVC = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
    [twitterPostVC setInitialText:@"てすと"];
    [self presentViewController:twitterPostVC animated:YES completion:nil];
}

1行目でTwitterの画面を生成し、2行目でテキストを突っ込み、3行目で呼び出しているだけです。
まぁSocialのメソッドなりクラスが勝手に画面を作ってくれます。便利です。
Runしてボタンをクリックして以下のような画面が出れば成功です。

f:id:tereka:20140225094655p:plain

タイムラインの表示

さて、後はコードを書くだけです。

viewDidLoad

- (void)viewDidLoad
{
    [super viewDidLoad];
    //最近、APIが変更になったのでurlは気をつけましょう。
    NSString *apiURL = @"https://api.twitter.com/1.1/statuses/home_timeline.json";
    
    ACAccountStore *store = [[ACAccountStore alloc] init];
    ACAccountType *twitterAccountType =
    [store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
    
    [store requestAccessToAccountsWithType:twitterAccountType options:nil completion:^(BOOL granted,NSError *error){
        if(!granted){
            NSLog(@"Twitterの認証を拒否");
        }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:apiURL];
                SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
                                                        requestMethod:SLRequestMethodGET
                                                                  URL:url parameters:params];
                [request setAccount:account];
                [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
                [request performRequestWithHandler:^(NSData *responseData,NSHTTPURLResponse *urlResponse,NSError *error) {
                    if(!responseData){
                        NSLog(@"response error: %@", error);
                    }else{
                        NSError *jsonError;
                        tweets = [NSJSONSerialization JSONObjectWithData:responseData
                                                                 options: NSJSONReadingMutableLeaves error:&jsonError];
                        if(tweets){
                            dispatch_async(dispatch_get_main_queue(), ^{ // 追加
                                [self.tableView reloadData]; // 追加
                            });
                        }else{
                            NSLog(@"%@", error);
                        }
                    }
                }];
            }
        }
    }];

viewDidLoadメソッドはページ実行時に実行されるメソッドです。
ポイントをいくつかご紹介します。

apiURL

apiURL 今年にTwitterのプロトコルが変更され、httpからhttpsに変更されました。その都合でいくつかのブログのurlでapi動作しないことがあります、気をつけましょう。

requestAccessToAccountsWithType

認証を要求する箇所。
grantedの中にTrue or Falseが入っている。

SLRequest

APIをたたくクラス、Oathなどをスルーしてくれるそうです。

NSJSONSerialization

返ってきたデータを変換する。

dispatch_async

同期的に実行する為のメソッド

self.tableView reloadData

データをリロードする。テーブルを再描写

numberOfSectionsInTableView

繰り返しのセルの数

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

numberOfRowsInSection

行数を突っ込めば大丈夫です。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [tweets count];
}

cellForRowAtIndexPath

セルの内容を決めるメソッドです。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    // Configure the cell...
    if(cell == nil){ 
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    } 

    NSDictionary *status = [tweets objectAtIndex:indexPath.row];
    NSString *text = [status objectForKey:@"text"];
    
    cell.textLabel.text = text;
    
    return cell;
}

描写するセルなどを指定しています。

f:id:tereka:20140225124122p:plain

参考文献

http://qiita.com/paming/items/9a6b51fa56915d1f1d64
http://www.appbank.net/2012/06/30/iphone-news/434166.php
http://ios.rainbowapps.jp/text_dev/10

次回はさすがにユーザー名を表示をしたいのでカスタムセル周りを対策したいと思います。

TwitterViewController

TwitterViewController.h

#import <UIKit/UIKit.h>

@interface TwitterViewController : UITableViewController{
    NSArray *tweets;
}
- (IBAction)TwitterButton:(id)sender;

@end

TwitterViewController.m

#import "TwitterViewController.h"
#import <Social/Social.h>
#import <Accounts/Accounts.h>

@interface TwitterViewController ()

@end

@implementation TwitterViewController

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"TweetCell"];
    //最近、APIが変更になったのでurlは気をつけましょう。
    NSString *apiURL = @"https://api.twitter.com/1.1/statuses/home_timeline.json";
    
    ACAccountStore *store = [[ACAccountStore alloc] init];
    ACAccountType *twitterAccountType =
    [store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
    
    [store requestAccessToAccountsWithType:twitterAccountType options:nil completion:^(BOOL granted,NSError *error){
        //Twitterの認証の拒否or認証
        if(!granted){
            NSLog(@"Twitterの認証を拒否");
        }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:apiURL];
                SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
                                                        requestMethod:SLRequestMethodGET
                                                                  URL:url parameters:params];
                [request setAccount:account];
                [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
                [request performRequestWithHandler:^(NSData *responseData,NSHTTPURLResponse *urlResponse,NSError *error) {
                    if(!responseData){
                        NSLog(@"response error: %@", error);
                    }else{
                        NSError *jsonError;
                        tweets = [NSJSONSerialization JSONObjectWithData:responseData
                                                                 options: NSJSONReadingMutableLeaves error:&jsonError];
                        if(tweets){
                            dispatch_async(dispatch_get_main_queue(), ^{ // 追加
                                [self.tableView reloadData]; // 追加
                            });
                        }else{
                            NSLog(@"%@", error);
                        }
                        //Tweet取得完了に伴い、Table Viewを更新
                    }
                }];
            }
        }
    }];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [tweets count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if(cell == nil){ 
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    } 
    
    NSDictionary *status = [tweets objectAtIndex:indexPath.row];
    NSString *text = [status objectForKey:@"text"];
    
    cell.textLabel.text = text;
    
    return cell;
}


- (IBAction)TwitterButton:(id)sender {
    SLComposeViewController *twitterPostVC = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
    [twitterPostVC setInitialText:@"てすと"];
    [self presentViewController:twitterPostVC animated:YES completion:nil];
}
@end