2013/12/7更新。iOS 7に対応しました。

UITableViewはよく使われるものでありながら、最初は扱いが難しいものです。今回は数回にわたってUITableViewについて説明したいと思います。またiOS 7、Auto Layoutに対応しています。

サンプルコードを交えて説明します。GitHubからダウンロードもしくはcloneして下さい。

https://github.com/stack3/UITableViewSamples

サンプルを起動したら、Simple TableViewを選択してください。

このような一覧が表示されます。

01

スクロールさせるとItem99まで表示されます。つまり0〜99で100個の項目が表示されています。

02

ただ一覧表示するだけのサンプルです。それではプログラムの方を見てみましょう。

STSimpleTableViewController.storyboard

UITableViewを配置しているstoryboardを見てみましょう。

03

Constraintはこのようになっています。

07

画面を回転させても、全体表示を保つようになっています。今回はAuto Layoutの説明が趣旨ではないので細かい説明は割愛します。

※ Auto Layoutについては、こちらのチュートリアルで詳しく説明しています。

ソースコードを見る前にUITableViewDataSource、UITableViewDelegateについて説明します。

UITableViewDataSourceプロトコル

DataSourceは直訳すると「データの源」という意味になります。まさにこのプロトコルはUITableViewへデータの情報を受け渡す役割を果たします。

主にUITableViewをメンバ変数(プロパティも含む)としてもつViewControllerが、このプロトコルを実装します。ViewControllerはUITableViewで表示するデータを管理し、UITableViewDataSourceプロパティのメソッドを通じて、必要な情報をUITableViewに渡します。

以下の図のようにイメージするとよいでしょう。

08

「UITableViewさんがどうするか聞いてるよ」の部分がUITableViewDataSourceのメソッド呼び出しであり、「こうしてって言っておいて」の部分がViewControllerでメソッド実装というイメージです。

たとえば代表的なのが表示する行数はいくつにするかというUITableViewからの問い合わせですが、それは以下のメソッドで行われます。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

このUITableViewDataSourceのメソッドで表示する行数を戻り値で返します。

UITableViewDelegateプロトコル

DataSourceがデータの情報をやりとりするのに対して、Delegateは主にイベント的な通知を行うために使われます。DataSource同様に主に通知の受取先はViewControllerになります。

09

受取先のViewControllerは受け取ったイベントに対して適切な処理をします。つまりDelegateのメソッドを実装します。

代表的なのは行を選択した時のUITableViewからの通知ですが、それは以下のメソッドで行われます。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

多くの場合、UITableViewから見ると受取先に「後のことはまかせた」という扱いになります。よって、上の図の矢印は一方通行にしています。

ただし、そうでない場合もあります。例えば行の高さをどうするかというDelegateメソッドは戻り値で高さを返す必要があり、それはUITableViewへ伝えられます。(これはUITableViewDataSourceのメソッドでも良かった気がするのですが・・・)

STSimpleTableViewController.h

STSimpleTableViewControllerクラスで、画面処理を行なっています。このクラスはUITableViewとUITableViewDataSourceを通じてやりとりするので、以下のように宣言します。

@interface STSimpleTableViewController : UIViewController<UITableViewDataSource>

※ 今回はUITableViewDelegateは使いません

STSimpleTableViewController.m

tableView変数

UITableViewをプロパティとしてIBOutletで宣言しています。

@property (weak, nonatomic) IBOutlet UITableView *tableView;

StoryboardのUITableViewとひもづけてあります。

10

表示データの変数

各行に表示する文字列をNSMutableArrayへ格納するために、_rowsというメンバ変数を用意します。

@property (strong, nonatomic) NSMutableArray *rows;

awakeFromNibメソッドで_rowsにItem 0〜99の文字列を入れます。

_rows = [NSMutableArray arrayWithCapacity:100];
for (int i = 0; i < 100; i++) {
    NSString *title = [NSString stringWithFormat:@"Item %d", i];
    [_rows addObject:title];
}

tableViewの初期化

viewDidLoadで、tableViewの初期化を行います。

- (void)viewDidLoad
{
    [super viewDidLoad];

    [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:_STCellId];
    _tableView.dataSource = self;
}

UITableView#registerClass:forCellReuseIdentifierメソッド

UITableViewの各行の表示はUITableViewCellというクラス、もしくはそのSubclassを使います。以後、これらをセルと呼びます。

UITableView#registerClass:forCellReuseIdentifierメソッドで、セルのクラスを指定します。今回は標準のUITableViewCellクラスを使うので、上記のように[UITableViewCell class]を引数に渡しています。

第2引数にはセルを識別するIDを渡します。これは後述するセルの再利用と関係します。とりあえずここでは@”Cell”とします。後でもこのIDを使用するので以下のようにdefineしており、それを引数に渡しています。

#define _STCellId @"Cell"

UITableView#dataSourceプロパティ

UITableView#dataSourceへプロトコルを実装しているオブジェクトを、このプロパティへ渡す必要があります。

今回はViewControllerがプロトコルを実装しているので、上記のようにdataSourceプロパティへselfを渡しています。

UITableViewDataSourceのメソッド実装

UITableViewは、全部でいくつの行を表示すべきなのかを知る必要があります。以下のメソッドを通じて行われるので、ここで行の数を返します。つまり、_rowsの要素数です。

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

UITableViewは、今のスクロール位置で表示すべき行のViewを要求します。それはtableView:cellForRowAtIndexPath:を通じて行います。

ここはUITableViewを理解する上での難関の1つです。初めてだと何をやっているかわかりにくいでしょう。理解を進めるためにセルの再利用について説明します。

セルを再利用してメモリ浪費を抑える

UITableViewの各行はUITableViewCellを用いて表示します。tableView:cellForRowAtIndexPath:メソッドではUITableViewCell、もしくはそのSubclassを返す必要があります。

今回のように100行あった場合、100個UITableViewCellを生成して返すようだとメモリ効率がすごく悪くなります。なぜかというと実際に画面に表示される行は、100行の内の一部だけだからです。つまり表示に必要な分だけ生成されれば良いのです。

そこでUITableViewには以下のようにセルの再利用をする仕組みがあらかじめ備わっています。

04

06

UITableView#dequeueReusableCellWithIdentifier:forIndexPath

このメソッドは、指定したIndexPathの行を表示するためのUITableViewCell返します。返すときに再利用を考慮しています。引数に渡すreuseIdentifierは、再利用の判別に必要なIDです。

今回はUITableView#registerClass:forCellReuseIdentifierで渡したものと同じ_STCellID(@”Cell”)を渡しています。

行ごとに全く違う情報を表示しなければならない時もあります。その場合は、UITableViewCellのSubclassを複数作って表示する必要が出てきます。この場合、reuseIdentifierをSubclassごとに異なるものにすることになるでしょう。この件は後の記事で説明します。

UITableViewDataSource#cellForRowAtIndexPath:の実装

実装は以下のようになっています。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *title = [_rows objectAtIndex:indexPath.row];

    UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:_STCellId forIndexPath:indexPath];
    cell.textLabel.text = title;

    return cell;
}

_rowsからindexPathで指定された行のタイトルを得て、それをcell.textLabel.textへ代入しています。UITableViewCell#textLabelはUILabelオブジェクトです。このUILabelのtextプロパティを代入すると、それが表示されます。

以上、基本的なUITableViewの表示でした。

その2へつづく。