この記事はあすとろなんとか@astronaughts さん主催のTitanium Mobile Advent Calendar 2012 向けに書いています。
最初何を書こうかネタが思いつかなかったのですがTitanium Mobile: Create a Sliding Menu for iOSという記事を見つけてこういうUIの実装方法について解説していこうと思います
まず最初に作ったアプリのサンプルについて
上記の記事と全く同じものだとちょっとつまらないので左側にタブを3個配置して、タブをタッチするとメインのtableviewが右側に「にょい〜ん」とスライドして画面から消える。その後戻ってくる時にタッチしたタブとtableview背景色が同じ色になるサンプルを紹介しようと思います。
にょい〜んで雰囲気伝わる・・とは思えないのでキャプチャー撮りました。
if文が多くなりそうだけど、State パターンでスッキリ実装出来た
以前こんな記事を書いた時に
本音としては、こんな感じ↓でブロガー選択すると、右側からにょいーんとエントリ一覧が出てくる今風(というかPathをパクったものっw)なUIで実装したかった
というようなことを呟いてました
GUIなアプリを作る時に、最適な実装方法が無いか昔から悩んでいたのですが、最近になってようやくGoFデザインパターンのState パターンの活用方法が理解でき結果として、if文多様しなくてもスッキリとした実装になりましたのでそのあたり掘り下げて紹介します。
(注)ソースコードはCoffeeScriptでの解説になりますが、CoffeeScriptからJavaScriptに変換した後のソースをGistにアップしてます。
それぞれのファイルの役割
今回以下3つのファイルを使ってます
- app.coffee
- controller.coffee
- state.coffee
app.coffeeで、今回のサンプルアプリで利用するUI群の生成と、イベント管理をするcontroller、後述する状態管理をするstateの読み込みをします
そもそも、なぜState パターン?
19.State パターンという記事に
State パターンとは、 モノではなく、「状態」をクラスとして表現するパターンです。〜中略〜状態の変化に応じて振る舞いが変わるような場合に威力を発揮するパターンです。
ということが書かれてます。
今回作ったサンプルアプリだと左側のメニューが選択された状態をクラスとして表現してあげることで、見通し良いソースコードになるかと思って取り入れてみました。具体的には、状態をクラスとして表現するため、以下の様な設計にしました
- tab1State
- tab2State
- tab3State
- baseState
tab1State、tab2State、tab3Stateはそれぞれタブ1,タブ2,タブ3が選択された時の状態を表現するためのクラス。
タブ1,タブ2,タブ3 のそれぞれに共通するanimateメソッドをここで定義し、タブをタッチした時のメインのtableviewが右側に「にょい〜ん」とスライドして画面から消えて、戻ってくる時にタッチしたタブとtableview背景色変更する処理を実装してます
処理の流れを追いながら、もう少し掘り下げた解説
起動してからtab2が選択されるまでの流れを解説することでそれぞれの役割が理解できるかと思うので順番に解説していきます
起動時
起動時、つまり、app.coffeeが読み込まれた段階ですがapp.coffeeの一部コードを一部抜粋します
Controller = require("controller") baseState = require("baseState") state = default:1 slide:2 end:3 # 中略 controller = new Controller() # 中略 tab1.addEventListener('click',(e) -> controller.handleEvent(state.default) ) # 中略 tab2.addEventListener('click',(e) -> controller.handleEvent(state.slide) ) # 中略 tab3.addEventListener('click',(e) -> controller.handleEvent(state.end) )
という感じで、必要なファイルの読み込みとcontrollerを初期化してます。
controllerを初期化した段階では以下のようにbaseStateクラスのstartメソッドが実行されてます。
@state = new baseState() @state = @state.start()
baseStateクラスのstartメソッドで
return new tab1State()
としているのでcontrollerを初期化タイミングで、tab1Stateが選択された状態になっています。この状態でtab2がクリックされたあとの処理をみていきます
tab2クリック時の動作
tab2クリックした時に、割り当てられているイベントが発火して、引数にstate.defaultを渡した上で、controllerのhandleEventメソッドが実行されます
処理がhandleEventメソッドに移りますが、引数に渡した値(この場合だとstate.slide)にマッチするselectTab2メソッドが実行されます
handleEvent:(event) -> Ti.API.info event if event is state.default @state = @state.selectTab1() else if event is state.slide @state = @state.selectTab2() else @state = @state.selectTab3()
@state.selectTab2は、@stateが格納されているのはこの段階では、tab1クラスから生成されるオブジェクトのため、tab1StateクラスのselectTab2が実行されます。
つまり
selectTab2:() -> animate("#ededed") super
ですね。最初のanimate() は独自に作ったメソッドで、引数にTableViewに設定したい背景色を指定することでTableViewのアニメーションを実施してます。アニメーション終了後にsuperが呼び出されるので、親クラスの selectTab2()が呼び出されます
つまり
Ti.API.info "START NEW TAB2STATE" return new tab2State()
ですね。これ以降は、Tabをクリックした時に紐付くイベント発火されていきますが、ロジックは同じ考えになってます。
ここまで解説記事書いたけど、どうも自分の理解が100%完璧でない所もあるので、イマイチ伝わりづらいかもしれませんが、以下にソースコードや、他の言語でのStateパターン実装のサンプルなどを見ていただて、理解を深めてもらえればなぁと思ってます
6日目は@mumumuさんです、よろしくお願いしますー