リフレッシュコントロールというやつを使ってみる

OS X 10.7, iOS 6.1, Xcode 4.6

なにそれ?

ビュー(UITableView)を下にスライドさせることによって、更新アクションを起こすアレです。

こういうやつ。

↑のこれをやりたかったんですけど、最初、これの名前が分からなかったので、どう実装すれば良いのかも調べられませんでした。これって、「Refresh Control」とか「Pull-to-Refresh」っていうんですって。

名称さえわかれば、あとはGooglingすれば済む話ですが、せっかくなので、使い方を書いておきます。


テスト用プロジェクトを新規作成

リフレッシュコントロールを使うには、まず、テーブルビューを構成する必要があります。
わざわざプロジェクト作成から書くこともないでしょうが、一応・・・

Xcode起動
・Create a new Xcode project
・Single View Applicationを選択
・適当なプロジェクト名を入力して「Next」、「Create」

<以下、ストーリーボード上での作業>
・元から存在したView Controllerを削除する
・代わりに、TableViewControllerを設置する
・Table View Controllerにナビゲーションコントローラーを設定する
 (やり方)
  Table View Controllerを選択した状態で、メニューから、
  「Editor」→「Embed In」→「Navigation Controller」を選択する。

・セルのプロトタイプは使わないのでなしにする
 (やり方)
  ストーリーボードの左側の「Table View Controller」を展開して、
  「Table View」を選択し、右側の「Attributes Inspector」を選択、
  「Prototype Cells」を0に設定する。

・ナビゲーションバーに適当なタイトルをつける(ここではPizza Menuとしました)
 (やり方)
  ストーリーボードの左側の「Table View Controller」を展開して、
  「Navigation Item」を選択し、右側の「Attributes Inspector」を選択、Titleに文字列を設定する。

<ソースファイルの追加、削除>
・元々あったViewController.h, ViewController.mを削除する
・メニューから「File」→「New」→「File...」を選択
・「Objective-C class」を選択して「Next」
・クラス名は適当に設定(ここではPizzaViewControllerとしました)
・「Subclass of」にUITableViewControllerを指定して「Next」
・ソースファイルの保存場所を指定して「Create」

<ストーリーボードで画面とソースを関連付ける>
・Table View ControllerのソースにPizzaViewControllerを指定する
 (やり方)
  ストーリーボードの左側の「Table View Controller」を選択して、
  右側の「Identity Inspector」を選択、ClassにPizzaViewControllerを設定する。

ここまで作業した状態で、以下のようになっています。


テストデータを表示する

見た目が何もないのも寂しいので、適当にレコードを表示してみます。

・PizzaViewController.hに表示するレコードの配列を属性として定義

#import <UIKit/UIKit.h>

@interface PizzaViewController : UITableViewController
{
    NSArray* records;
}

@end

・PizzaViewController.m
 viewDidLoadでレコードを初期化する

-(void)viewDidLoad {
    [super viewDidLoad];
	
    records = [[NSArray alloc] initWithObjects:
                     @"Margherita",
                     @"Giga Meat",
                     @"Tropical",
                     nil];
}

・PizzaViewController.m
 didReceiveMemoryWarningでレコードを解放する
 (テストコードだけど、一応ね)

-(void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    [records release];
    records = nil;
}

・PizzaViewController.m
 セクションは使わないので1個にしておく

-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
    return 1;
}

・PizzaViewController.m
 セルの数は配列のデータ数と一致するようにする

-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
    return records.count;
}

・PizzaViewController.m
 セルの内容を生成する

-(UITableViewCell*)tableView:(UITableView *)tableView
	   cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString* CellIdentifier = @"Cell";
    UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if (!cell) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                    reuseIdentifier:CellIdentifier] autorelease];
    }
	
    // Configure the cell...
    cell.textLabel.text = [records objectAtIndex:indexPath.row];
    return cell;
}

この状態で実行して、表示を確認すると、下のようなごく当たり前の画面が出ます。

今の段階では、まだ、スライド操作をしてもなにも出ません。

これで下準備完了。ここまでは、普通のテーブルビューの使い方です。
ここからが本題です。


ストーリーボード上で、テーブルビューの設定を行う

ストーリーボードの左側の「Pizza View Controller」を選択して、
右側の「Attributes Inspector」を見ると、「Table View Controller」の設定項目の中に、
「Refreshing」という設定が「Disabled」になっています。

これを「Enabled」に変更して、スライドしたときに、「更新」と日本語で表示するようにしてみます。
「Tint」で色を変えられるみたいですが、今回は見慣れた色のデフォルトにしておきます。

これでスライドしたときに下のような画像が出るようになりました。

しかし、このアクションが行われたことを、プログラム内でどのようにして知ることができるでしょうか。


更新処理を実装する

更新処理の実装は、一般的なアクション通知の機構に従って行います。
PizzaViewController.mに、戻り値void、引数なしのメソッドを1つ作成します。ここでは、updateRecordsというメソッドにしました。こいつが、スライド時のイベントを処理するようにします。

-(void)updateRecords {
    // ここに更新処理を書く
}

で、viewDidLoadで、リフレッシュコントロールに、上で作成したメソッドのセレクタを渡してやります。

[self.refreshControl addTarget:self action:@selector(updateRecords) forControlEvents:UIControlEventValueChanged];

これでUIのアクションをプログラム内のメソッドに関連付けることができました。

さて、updateRecords内でテーブルの更新処理を行えばいいのですが、現実的には、別スレッドで通信を行うようなことが必要になるでしょう。ここでは、通信までは実装しませんが、別スレッドで時間のかかる処理を実行することを想定してコードを書いてみます。


マルチスレッドで擬似的な通信処理

アクション通知 → スレッド起動 → 通信処理 → レコード更新の順に行うこととします。
通信処理以外は、すべてメインスレッドの処理になります。

-(void)updateRecords {
    // 別スレッドで何か時間のかかる処理を実行するとする
    [NSThread detachNewThreadSelector:@selector(beginCommunication:) toTarget:self withObject:nil];
}

-(void)beginCommunication:(id)params {
    // ここで通信などを行う前提だが、サンプルなので、適当にレコードを追加するだけにしておく
    [NSThread sleepForTimeInterval:1.5];
	
    NSMutableArray* newRecords = [[[NSMutableArray alloc] initWithArray:records] autorelease];
    [newRecords addObject:@"Domino's Deluxe"];
    [newRecords addObject:@"Spicy Deluxe"];
    [newRecords addObject:@"Mega Veggie Pizza"];
    [newRecords addObject:@"American Special"];
	
    [self performSelectorOnMainThread:@selector(endCommunication:) withObject:newRecords waitUntilDone:NO];
}

-(void)endCommunication:(NSArray*)newRecords {
    // レコードの配列を更新する
    [records release];
    records = [[NSArray alloc] initWithArray:newRecords];
	
    // テーブルビューを更新する
    [self.tableView reloadData];
	
    // リフレッシュコントロールに終了を通知する
    [self.refreshControl endRefreshing];
}

こんな感じになりました。

更新中

更新後


上記のコードはいんちき更新処理なので、このままでは、更新するたびに同じレコードが4つずつ増えていってしまいます。まあ、その辺は実際のアプリではちゃんとやるとして、いっちょ雛形としてはこんなもんでどうでしょ。



〜 おしまい 〜