Ruby Motionチュートリアルやってみた(5)



Ruby Motionチュートリアルをやってます。前回の記事はこちら

Ruby Motionチュートリアルはこちら。

▼開発環境
Ruby 2.5.3
Xcode 10.1
Ruby Motion 5.16

今回は、テーブルについてのお話だそうです。といっても、データベースに作成するテーブルとは別物だそうですよ。

どっちかというと、HTMLで使うテーブルみたいなイメージです。

ほら、YouTubeとかでも動画一覧を表示してるじゃないですか。あんな感じ。

んで、テーブルを管理するライブラリがUITableViewだそうです。

ちょっと頭を切り替えなきゃいけなくて、これまでコントローラーについてずっとやってきたけど、これはViewなのでframeを持ったサブビューとして追加しなきゃいけないそうですよ。

あと、delegateとdataSourceオブジェクトを割り当てないといけないそうです。うん、なんのこっちゃ。。。しかも、チュートリアルが挑発してくるし。。。

テーブルビューはオブジェクトのそれらメソッドを呼び出します。どうだろう、わかった?コードを書いてみましょう。

何はともあれ、指示通りにやってみましょう!

./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オブジェクトで返されるそうです。

まぁ、要するに画面いっぱいに指定するってことっすね。

次は、タブコントローラーに作成したコントローラーを組み込むように設定しましょう。

 ...
 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
 ...

tab_vontrollerの先頭にテーブルのコントローラーを配置してるだけですね。

この状態で一度、実行してみましょう。

下のタブにアルファベットが入りました!うん、ちゃんとテーブルっぽいのが表示されてますね♪いいんじゃないでしょうか。

さ〜て、いよいよ肝心のテーブルの中身を作成して行きましょう!

んで、よくわかんなかった、dataSourceの設定が出てきます。

...
 def viewDidLoad
   ...
   @table.dataSource = self
 end
...

んで、ちょっとこいつが曲者で、以下のメソッドを実装しなきゃいけない。

tableView:cellForRowAtIndexPath:
tableView:numberOfRowsInSection:

他にも使用できるメソッドはあるみたいだけお、アップルのドキュメントを確認しろってさ。

ということで、早速用意しましょう!ちなみに、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:」とは何ぞやって説明に入るみたいですね。

これは、「NSIndexPathクラスのindexPathってオブジェクト」と、「UITableViewCellのオブジェクト」の2つを返すらしいよ。

んで、また「NSIndexPath」ってわかんないので出てきた。。。

NSIndexPathは、sectionとrowというプロパティを持っていて、私たちが何行目のデータを与えるかを示しているらしいよ。

んー、結局何すればいいんだ。。。

とりあえず、これをやんなきゃいけないらしい。

cellForRowAtIndexPath は、新しいセルを作成するか画面外に移動したセルを再利用し indexPath で示されるデータを設定したのち、セルのオブジェクトを返す必要があります。

↑で言いたいことはわかるけど、じゃあそれを実装するにはどうしたらいいのかがよくわからんのよな〜。にも関わらず、なんか新しいクラスが説明されるという。。。

UITableViewCell は実際に私たちのテーブルに表示される UIView のサブクラスです。デフォルトの実装では、textLabel や imageView といったいくつかの便利なフィールドが付属します。独自の見た目や振る舞いを追加するために UITableViewCell をサブクラス化できますが、通常のクラスを使用して理解を得ることができるでしょう。

UITableViewCellは、テーブルのセルの見た目を整えるのに使うって感じかな?

まぁ、とりあえずコードをみてみましょうか!

 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

.................................。

うん。一行ずつ見て行くしかないね。。。(´;Д;`)

とりま、「||=」はいいよね?「A ||= B」のとき、AがnilまたはfalseならAにBを代入するって意味。ここでは、インスタンス変数に「CELL_IDENTIFIER」って文字列を代入してるってことだね。うん!次の行!

えっと、この文を簡単にすると、

cell = A || begin 
  B 
end

ってことかな。ん?beginって例外処理くらいにしか使ったことないけど、rescueもensureも出てこないぞ?

この変、適当に覚えてた人、僕だけじゃないよね?

そして、調べて見たけど、例外処理の話しか出てこなくて、なんでbegin使ってるのかよくわから〜ん!!(誰か教えてください。。。)

とりま、Aがnilならbegin~endを実行するのはわかる。いったん、保留!

中身を見ていこう!

「tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier)」これ!

えーと、tableViewは自分のメソッドだよね。いわゆる再帰処理。次の「dequeueReusableCellWithIdentifier」は、識別子を指定してUITableViewCellを取得するためのものなんだって。

んで、UITableViewCellに一個もセルがなかったら、nilを返すメソッドだそうです。

ふむふむ、なんとなくわかってきたぞ。次はbegin~endの中身だね!

UITableViewCellは、確かセルの見た目を指定するクラスだったよね。んで、「.alloc.initWithStyle」ってことは、セルの見た目の初期値を指定してるってことかな。

「UITableViewCellStyleDefault」は例のごとく、デフォルトのデザインってことなんだろうなぁ。

「reuseIdentifier」ってなんだろ。チュートリアルを引用。

reuseIdentifier とは何でしょう?ええと、UITableView はテーブルで使用される各セルの種類ごとに識別子を与えることで「再利用」する細工がなされています。再利用するセルを取得するとき、実際には同じ識別子を持つセルをプールしているところからそれらを取得します。そのため、もし何らかの理由によりすべての行に対してユニークな識別子を与える場合、識別子ごとのプールにはセルが 1 つずつしかないため再利用できるセルは取得できません。もし視覚的にそして種類も全く異なる 2 つの行を扱う場合には、おそらく 2 つの識別子を持つことになるでしょう。わかりました?

うーん、識別子でセルの種類を区別して、同じ識別子のやつなら「再利用」ができるってことだよね。。でも、「実際には同じ識別子を持つセルをプールしているところからそれらを取得します。」って書いてる、プールしてるところってどこやねん。

あんま深く考えずに、そういう魔法なんだと思えばいいのかな?

最後の行!「cell」

まぁ、これは取得したセルの情報を返すってことだけだよね。「cellForRowAtIndexPath」はセルの情報を返すメソッドになってると。

う〜〜〜〜ん、ちょっとここらの理解曖昧かもなー。

とりあえず、次!

次は、実際にセルにデータを入れてみようってお話。

チュートリアルでは、テーブルにアルファベットを順番に入れているみたいですね。

def viewDidLoad
 ...
 @data = ("A".."Z").to_a
end

うん、データの記述はこんだけ。本来、ここがメインになるはずだけど、まぁチュートリアルだからね。

さぁて、忘れてはいけないのがもう一つのメソッド「numberOfRowsInSection:」だ。名前からして、セクションの個数をけってするのだろうか?

とりあえず、コードを記述。

def tableView(tableView, numberOfRowsInSection: section)
 @data.count
end

@dataの数をカウントするだけ。アルファベットだから24が返ってくるはず。

とりあえず、この状態で実行してみよう!

うん!綺麗に表示されている!そして、チュートリアルではnumberOfRowsInSection:の説明が以上終わりww

セルの個数を決める?って仮説を検証するために、返り値を5に変更してもう一度実行してみるね。

def tableView(tableView, numberOfRowsInSection: section)
 # @data.count
 5
end

お!!セルの個数が5個になった!!やっぱnumberOfRowsInSection:は、表示するセルの個数を決めるメソッドだったんだね〜。スッキリしたw

さてさて、表示しただけじゃなくて今度はタップしたらなんか動作するようにしましょう。

そのために思い出したいのが、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

んで、このメソッドをどのように使用しているかを見ていけば良いわけね。

まず、一行目の「deselectRowAtIndexPath:animated」が何をしているのか。チュートリアルによると、UITableViewはデフォルトだと、タップしたところをハイライトし続けるらしいので、それを解除してあげなきゃいけないそう。

そのために、deselectRowAtIndexPath:animatedを使うんだって。

要するに「タップ(didSelectRowAtIndexPath)すると、最初にタップ解除(deselectRowAtIndexPath)をする」ということか。

んで、次のalertの文。

...?あれ?これどっかで見たことある!チュートリアルの最初の方でやったアラートを出すやつだ!

うん、とりあえずわからないところはないかな。

んじゃ、実際に実行してみよう!

おー!良い感じ♪

というわけで、テーブルの章は終わりです。

まとめ

W-w-w-wrap up
UITableView の基本についてカバーしました。より良いものにするためにカスタマイズや振る舞いの変更を行うことができます。より詳細な記事についてはいつの日にか。

今日学んだことは何だったでしょう?

・UITableView は効率的な方法で似たようなデータを行に表示します。
・UITableView には情報を提供するために dataSource が、そしてユーザの操作と look-and-feel を扱うために delegate があります。
・dataSource には 2 つの実装が必須なメソッド numberOfRowsInSection と cellForRowAtIndexPath があります。cellForRowAtIndexPath は UITableViewCell のインスタンスを返す必要があります。
・ユーザが行をタップしたことを検出するために delegate メソッドの didSelectRowAtIndexPath を使うことができます。

次は、アニメーションについて勉強して行きましょう!



サポートしていただけると、泣いて喜びます! 嬉しくて仕事をめちゃめちゃ頑張れます。