Tables
私たちはビュー、コントローラ、コンテナコントローラと順にたどってきましたが、ここでは道を逸れもう少し独特なものを得たいと思います。UITableView
クラスはほとんどの iOS アプリにとって非常に重要なものです。 1 つの章 (たぶんもっと多くの章が必要でしょう!) として十分値するものです。
みなさんの iPhone や iPad に搭載されているデフォルトのアプリに目を通してみましょう。青や黒のバーよりもさらに共通の傾向があることがわかるでしょう。それはスクロールするリストです。Phone.app は巨大なコンタクトリストをなめらかで使いやすくスクロールできます。SMS.app はほとんど青と緑のメッセージをスクロールできます。見た目はまったくことなりますが、これらは同じツールで構築されています。それは UITableView
です。
UITableView
はプレゼンテーションと似ていて、スクロールするコンテンツを構築します。UITableView
は非常にたくさんのアイテムを持つことができます。ごく少数から非常に大量のアイテムまでスケールする秘訣は、常に決められた一定数のサブビューのみをメモリで使用するからです。複雑そうなので、細かく見ていきましょう。
Mail.app で、みなさんは受信トレイに何百通もメールがある可能性がありますが、画面上にはその一部 (6 通くらい) しか表示されません。スクロールすると、一番上のメールはゆっくりと画面外へ移動します。テーブルビューは隠れてしまったこれらのビューを持ち、そして画面の 反対側 の端へそれらを移動します。画面の上部を移動したこれらのメッセージビューは、画面の下部に再配置されます。そして配置された新しい位置に適したデータで内容が置き換えられます。テーブルビューは信じられないほど高速にそれらの行を移動します。そのため、テーブルに数百ものアイテムがあったとしても、ひとにぎりの行のビューがメモリに保持されているだけとなります。
わかっていただけたと願います。
それでは、このすばらしいクラスをどのように利用するのでしょう?UITableView
はほかの UIView
のようにビューなので、適切な frame を持ったサブビューとして追加する必要があります。また、delegate
と dataSource
オブジェクトを割り当てる必要があります。これらのオブジェクトはいくつかのメソッド (必要に応じてもっと多く) の実装を必要とします。テーブルビューのすべての行を描画するために必要な情報を取得するため、テーブルビューはオブジェクトのそれらメソッドを呼び出します。どうだろう、わかった?コードを書いてみましょう。
Containers の章で作成したほかの UIViewController
を覚えていますか?データを渡すために UITableView
を使用するコントローラで置き換えていきます。そのコントローラクラスを作っていきましょう。
./app/controllers/AlphabetController.rb
で AlphabetController
を作成し、次の初期段階の実装を行います。
class AlphabetController < UIViewController
def viewDidLoad
super
self.title = "Alphabet"
@table = UITableView.alloc.initWithFrame(self.view.bounds)
self.view.addSubview @table
end
end
今ではおなじみのものですね。view.bounds
が少し新しいでしょうか。view.frame
のように、ビューの bounds
が CGRect オブジェクトで返されます。しかし、それは空の座標原点となっています。基本的に、それは位置情報を持たないビューのサイズを示します。それで newView.initWithFrame(view.bounds)
を実行するときに、newView
に自身のサイズで view
を覆い尽くすようになります。
AppDelegate
へ戻り、タブコントローラをこの新しいコントローラを使用するように変更しましょう。
...
controller = TapController.alloc.initWithNibName(nil, bundle: nil)
nav_controller = UINavigationController.alloc.initWithRootViewController(controller)
alphabet_controller = AlphabetController.alloc.initWithNibName(nil, bundle: nil)
tab_controller = UITabBarController.alloc.initWithNibName(nil, bundle: nil)
tab_controller.viewControllers = [alphabet_controller, nav_controller]
@window.rootViewController = tab_controller
...
必要なことは、新しく AlphabetController
を作成し、tab_controller
の viewControllers
でそれを先頭に配置することです。rake
を実行すると、愛しの (空っぽだけど) テーブルビューを見ることでしょう。
AlphabetController
を記述していく時間です。まずはじめに、テーブルビューの dataSource
としてコントローラを viewDidLoad
の最後で設定する必要があります。
...
def viewDidLoad
...
@table.dataSource = self
end
...
data source は以下のメソッドを実装しなければなりません。
tableView:cellForRowAtIndexPath:
tableView:numberOfRowsInSection:
使用できるメソッドはもっとありますが(Apple's docs でチェックしてください)、最も基本的なテーブルビューの dataSource
は上記のメソッドだけが必要です。それでは、早速それらを用意しましょう。
def tableView(tableView, cellForRowAtIndexPath: indexPath)
# return the UITableViewCell for the row
end
def tableView(tableView, numberOfRowsInSection: section)
# return the number of rows
end
cellForRowAtIndexPath:
は馴染みの無い 2 つのオブジェクト使います。一つは NSIndexPath
クラスの indexPath
、もう一つはメソッドが返す UITableViewCell
クラスのオブジェクトです。NSIndexPath
は section
と row
というプロパティを持っていて、何行目のデータを私たちが与えるのかを示します。
UITableViewCell
は実際に私たちのテーブルに表示される UIView
のサブクラスです。デフォルトの実装では、textLabel
や imageView
といったいくつかの便利なフィールドが付属します。独自の見た目や振る舞いを追加するために UITableViewCell
をサブクラス化できますが、通常のクラスを使用して理解を得ることができるでしょう。
cellForRowAtIndexPath
は、新しいセルを作成するか画面外に移動したセルを再利用し indexPath
で示されるデータを設定したのち、セルのオブジェクトを返す必要があります。実装したものは次のようになります。
def tableView(tableView, cellForRowAtIndexPath: indexPath)
@reuseIdentifier ||= "CELL_IDENTIFIER"
cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier) || begin
UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:@reuseIdentifier)
end
# put your data in the cell
cell
end
reuseIdentifier
とは何でしょう?ええと、UITableView
はテーブルで使用される各セルの種類ごとに識別子を与えることで「再利用」する細工がなされています。再利用するセルを取得するとき、実際には同じ識別子を持つセルをプールしているところからそれらを取得します。そのため、もし何らかの理由によりすべての行に対してユニークな識別子を与える場合、識別子ごとのプールにはセルが 1 つずつしかないため再利用できるセルは取得できません。もし視覚的にそして種類も全く異なる 2 つの行を扱う場合には、おそらく 2 つの識別子を持つことになるでしょう。わかりました?
みなさんは ||=
演算子を知らないかもしれませんね。Ruby では、a ||= b
は 「もし a
が nil か false なら、b
の値を代入する」という意味になります。上の例のように、オブジェクトにデフォルト値を与える、あるいは確実に 1 度だけ代入する良い方法です。
それで、識別子を指定して UITableViewCell
を取得するために dequeueReusableCellWithIdentifier:
を使用します。もしセルが 1 つも無ければ(dequeueReusableCellWithIdentifier:
は nil
を返します)、begin
/end
のブロックが実行されます。このブロックで、セルのスタイル (詳しくはこちらを参照) と reuseIdentifier
を指定し新しく UITableViewCell
を作成します。cellForRowAtIndexPath
メソッドの終わりでセルを返します。
次に何をするか予測がつかないのでしたら、私たちはテーブルにアルファベットの文字を埋め込んでいくつもりです。たいして役に立たないし難しくないけど、セルが再利用されるのを確認できるようにセットアップが簡単にできます。よりクリエイティブなデータを自由に使ってみてください(^_^)
viewDidLoad
で、行のタイトルとして使用する配列を初期化しましょう。Ruby は大変すばらしいので、1 行で素早くそれができます。
def viewDidLoad
...
@data = ("A".."Z").to_a
end
すべての大文字アルファベットが含まれた配列が作成されます。すばらしい、そうでしょ?
私たちは 2 つの data source メソッドを実装する必要があります。numberOfRowsInSection:
は、@data
を使うことしかしていないのでかなり単純です。
def tableView(tableView, numberOfRowsInSection: section)
@data.count
end
cellForRowAtIndexPath:
で、セルの textLabel
に対応する @data
の文字列をシンプルに設定します。
def tableView(tableView, cellForRowAtIndexPath: indexPath)
...
cell.textLabel.text = @data[indexPath.row]
cell
end
rake
を実行し、チェックしてみましょう。
悪くないよね?もう 1 つ作業し、ユーザの操作を受け付けるようにしてみましょう。
UITableView
は dataSource
と delegate
を持っていると言ったのを覚えているかな?AlphabetController
の delegate
も作ってみましょう。行をタップすると何かアクションすることができます。
UITableView
の dataSource
メソッドは一般的にテーブルへ情報やデータを提供します。delegate
メソッドはテーブルの見た目や読み込まれたデータに対するユーザの操作に関係します。ユーザによって行がタップされたことを検出するためにこれら delegate
メソッドの 1 つを使い、そしてアラートを表示します。
viewDidLoad
でコントローラの delegate
を実際に用意してみましょう。
def viewDidLoad
...
@table.dataSource = self
@table.delegate = self
...
end
テーブルの delegate
では必須なメソッドはありません。必要に応じて自由にいくつか 実装しましょう(必要がなければ実装しなくても良いです)。ユーザによってタップされた行を見つけ出すために、delegate
メソッドの tableView:didSelectRowAtIndexPath:
を使います。
def tableView(tableView, didSelectRowAtIndexPath:indexPath)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
alert = UIAlertView.alloc.init
alert.message = "#{@data[indexPath.row]} tapped!"
alert.addButtonWithTitle "OK"
alert.show
end
デフォルトでは、UITableView
はタップされた行を青色でハイライトし続けるので、それを解除するために deselectRowAtIndexPath:animated
が必要になります。それ以外の部分は、これまでに扱ってきたものばかりですね。
さぁ、いっきに rake
を実行してみましょう。
W-w-w-wrap up
UITableView
の基本についてカバーしました。より良いものにするためにカスタマイズや振る舞いの変更を行うことができます。より詳細な記事についてはいつの日にか。
今日学んだことは何だったでしょう?
UITableView
は効率的な方法で似たようなデータを行に表示します。UITableView
には情報を提供するためにdataSource
が、そしてユーザの操作と look-and-feel を扱うためにdelegate
があります。dataSource
には 2 つの実装が必須なメソッドnumberOfRowsInSection
とcellForRowAtIndexPath
があります。cellForRowAtIndexPath
はUITableViewCell
のインスタンスを返す必要があります。- ユーザが行をタップしたことを検出するために
delegate
メソッドのdidSelectRowAtIndexPath
を使うことができます。