Titanium で iPhone開発を始めるときに気をつけておきたいこと7つ

こんにちは、亀本です。

本当は今日はアシアルの日常を書く、という名目のブログ当番なんですが、どうせエンジニアの日常なんてコード書いてますよね。
ということで、最近使ってみたTitaniumについて書いてみる事にしました。

さて、最近ちまたでTitanium Mobileがあつい!という話が出ていて、入門記事もいろんなところで上がり始めていますね。
Titaniumをご存じない方のために簡単に説明しておくと、JavaScriptを使ってiPhone/Androidアプリを作ってしまおう、という物です。

果たしてどんなもんか!と、手元で作成中だったアプリをTitaniumを使って再実装してみたのですが、世間で言われるとおり所々で凝った事をしづらいものの、想像していたよりはずっと、リッチ・簡単・高速に作れる印象でした。
早い、安い、うまいなんてぎゅうどn(ry

その開発の際に、Objective-Cで実装していたときに比べて、「おっ?」と思うところがいくつかあったので、開発時に突っかからないように、気になったポイントをまとめてみました。
多分、当たり前の事も書いてある気もしますが、何か足しになれば幸いです。

なお、僕もTitaniumは初心者の域を脱していないので、おかしなところやよりよい方法がありましたら、がんがん突っ込みお願いします!

1. Ti.include()を過信しない

Ti.include()は外部のJSを組み込める、非常に便利なメソッドです。
ですが、これを過信してしまうと痛い目を見る事があるので、盲目的にならずに挙動に気をつけて使いましょう。

元来JavaScriptにはincludeが存在しないので、この関数の存在をみたときには狂喜乱舞したものです(※やや誇張表現が含まれます)。
が、この関数はC言語の#includeなどと同じで、Ti.include()を呼んだ位置に、呼び出したファイルの内容がそのまま展開されるような挙動を示します。

このとき、特に問題になりやすいのが、include対象となるJSファイルの中で、さらに外部のファイルを呼んでいる場合です。
先ほども書いたとおり、Ti.include()では呼び出した内容がそのまま展開されるので、呼び出されたファイル内でcreateWindow()やcreateImageView()などを呼んで、urlやimageを相対パスで指定していたり、同ファイル内で再度Ti.include()していたりすると、それらのパスが相対パスのまま展開されてしまいます。

そのため、呼び出すファイルのフォルダ階層が異なっていると、それらのファイルの読み込みエラーとなってしまいます。


app.js
tabs.js
main_windows/user.js
main_windows/login.js

というようなファイル群があって、tabs.jsの中で


var useWin = Ti.UI.createWindow({
url:'main_windows/user.js'
});

という処理があった場合、app.jsからtabs.jsをincludeした場合には、正常にuser.jsが呼び出せます。

しかし、ここでlogin.jsからtabs.jsをincludeした場合、login.jsはmain_windows/フォルダの中にいるので、上記の処理では main_windows/main_windows/user.js を呼び出そうとしてしまい、エラーとなってしまいます。

この点は、開発の設計時に気をつけておかないと、ファイルを分割してincludeする前提で作っていたのに、実装してみたらエラーになって方針変更を迫られる事になります。十分注意しましょう。

※なお、imageなどのリソースファイルに限って言うと、絶対パスを指定する事で回避は可能です。
 リソースファイルの格納先のパスがTi.Filesystem.resourcesDirectoryに保持されているので、これを使います。
 JSファイルは、残念ながらこの指定は通用しないようです。。。

2. TableViewでセクションを作る場合のプログラミングスタイルが大きく違う

これは、Objective-Cでの開発に慣れている人からすると、一瞬戸惑うところです。
(ただ、個人的にはこの方がすっきりする設計だと感じています。)

iPhone開発をしたことある方はよくご存じだと思いますが、TableViewは以下のような構造をしています。


TableView(本体) -> 複数のsection -> section毎に複数のRow

しかし、それにも関わらず、Objective-Cを使った開発ではTableViewのSectionの描画、Rowの描画が共にTableViewのdelgateメソッドとして実装されており、それらが順次呼び出される形で描画されます。

そのため、実装の流れを考える時には、イメージ的にはSectionもCellもフラットに取り扱う印象があり、各行はTableViewオブジェクトに直接配置するイメージ、Sectionのヘッダーなどは、その間に差し込んでいるイメージで開発していきます。(ここは人によって違うかもしれません)


TableView -> Section
          -> Row
          -> Row
          -> Row
          -> Section
          -> Row
          .....

一方、TitaniumではTableViewSectionというオブジェクトが存在し、TableViewとRowの間に挟まるので、


TableView -> TableViewSection -> TableViewRow
                              -> TableViewRow
                              -> TableViewRow
          -> TableViewSection -> TableViewRow
                              -> TableViewRow
                              -> TableViewRow
                              .......

という、本来あるべきオブジェクトの構造を持たせて実装していきます。

流れを紹介すると、下記のサンプルのようになります。


var win = Ti.UI.currentWindow;
var sections = [];
// first section
var section1 = Ti.UI.createTableViewSection({
 	headerTitle: 'first section'
});
var row1 = Ti.UI.createTableViewRow({
title: 'row1'
});
section1.add(row1);
var row2 = Ti.UI.createTableViewRow({
title: 'row2'
});
section1.add(row2);
sections.push(section1);
// second section
var section2 = Ti.UI.createTableViewSection({
 	headerTitle: 'second section'
});
var row3 = Ti.UI.createTableViewRow({
title: 'row3'
});
section2.add(row3);
var row4 = Ti.UI.createTableViewRow({
title: 'row4'
});
section2.add(row4);
sections.push(section2);
// table view
var tableView = Ti.UI.createTableView({
data:sections
});
win.add(tableView);

なお、よくサンプルなどでTableViewに直接TableViewRowを渡している場合もありますが、Sectionを使用しない場合には、そのような方法も可能です。

(また、単純に配列だけでTableViewのデータを定義する書き方もありますが、それらは入門サンプルに数多く乗っているので、ここでは言及しません。)

3. アニメーション後のViewには気を配る必要がある

Titaniumでは、メモリの保持・解放等類の概念が無いため、Window全体を回転させるようなアニメーションを行った場合に、アニメーション後に裏にまわったViewを破棄する、という手がありません。

そのため、アニメーション後にいろいろな遷移をしていると、裏側にアニメーション前に表に表示していたViewが見え隠れしてしまう、という事がありました。

(この点は、僕の使い方が未熟なのかもしれません。何か解決方法はあるのでしょうか。。。)

そのためアニメーションを混ぜる場合は、裏にまわったViewをどう扱うか、ちゃんと考えておく必要がありそうです。

4. View要素の座標指定方法は、layoutプロパティの有無で変化する

TitaniumでView上にボタンや画像などを配置していく場合、そのレイアウトの指定方法にhorizontal(水平配置)とvertical(垂直配置)、無指定の3種類があります。
この時、horizontal/verticalのいずれかを指定した場合と、一切の指定をしなかった場合で、座標指定の解釈が大きく変わります。

Objective-CでCGRectMakeなどを利用して配置を作っていく場合には、要素を載せるViewの左上を起点とした絶対座標で配置を行うことが多いです。
相対的な配置を行うには、他の要素のサイズを取得するなどの対処が必要になります。


CGRect iconFrame = CGRectMake(0, 0, 10, 10);
UIImageView *imageView = [UIImageView imageNamed:@"sample.png"];
[imageView setFrame:iconFrame];
[myView addSubview:imageView];
// CGRect textFrame = CGRectMake(0, 5, 10, 100); // これでは上部分がiconとかぶってしまう。
CGRect textFrame = CGRectMake(0, iconFrame.size.height + 5, 10, 100) // このようにする事で、アイコンの下に配置できる。
UILabel *textLabel = [[UILabel alloc] initWithFrame:textFrame];
[textLabel setText:@"samplesample"];
[myView addSubview:textLabel];

Titaniumでも、ベースにするViewにlayoutプロパティを設定せずにいると、左上を始点とする絶対座標での配置指定になります。
逆に、layoutプロパティにhorizontalかverticalを指定すると、各要素をブロックと見なし、相対座標を使った配置になります。


var win = Ti.UI.currentWindow;
var view = Ti.UI.createView({
layout: 'vertical', // これが指定されていないと、文字が重なる。指定してあると、縦に並ぶ。
top:0,
left:0,
width: 320,
height: 100
});
var label1 = Ti.UI.createLabel({
text:'test text',
top:0,
left:0,
height: 20,
width:'auto'
});
view.add(label1);
var label2 = Ti.UI.createLabel({
text:'test2 text',
top:0,
left:0,
height: 20,
width:'auto'
});
view.add(label2);
win.add(view);

・layoutプロパティなし

・layoutプロパティあり(vertical)

5. View要素のレイアウトには、horizontal(水平配置)とvertical(垂直配置)がある

上記でlayoutを指定すると相対座標で扱えるようになる、という話をしましたが、このレイアウトの相対座標のパターンとして、horizontal(水平配置)レイアウトとvertical(垂直配置)レイアウトの2種類が存在します。

verticalレイアウトは、上の例で紹介したように、各要素をブロックのように解釈して、順番に縦に並べていきます。

これをhorizontalレイアウトにすると、次々に横に並ぶようになります。


var win = Ti.UI.currentWindow;
var view = Ti.UI.createView({
layout: 'horizontal',
top:0,
left:0,
width: 320,
height: 100
});
var label1 = Ti.UI.createLabel({
text:'test text',
top:0,
left:0,
height: 20,
width:'auto'
});
view.add(label1);
var label2 = Ti.UI.createLabel({
text:'test2 text',
top:0,
left:0,
height: 20,
width:'auto'
});
view.add(label2);
win.add(view);

ちなみに、親に当たるViewのwidthを決めておくと、horizontalで並べた要素がその幅を超えた場合には、勝手に折り返してくれます。

これはなかなか便利な機能なので、画像をhorizontalレイアウトにしてひたすらaddしまくっていくだけで、タイル上に画像を並べたレイアウトなどが実現できてしまいます。

6. tableHeaderViewやtableFooterViewは使えない

Objective-Cでは元々、TableViewがtableHeaderViewとtableFooterViewという要素を持っており、これを使う事でTableViewの上下にViewを設置する事が出来ます。

ですが、TitaniumではこのViewを設定するプロパティが存在せず、TableViewと他のViewを組み合わせたい場合には、他のView(ScrollViewなど)の上に各Viewを貼り付けて対応する事になります。

この話自体はたいした事ではありませんが、上記に代表されるように、ちょこちょこと利用できない機能が存在します。
実装を考えるさいには、出来るだけポピュラーな機能で実現できるように考えておくと良いでしょう。

7. 有用なドキュメントとサンプルをしっかり押さえておく

公式のドキュメントは当てになるのかどうか微妙なんですが、よく知られているようにTitaniumは非常に強力な公式サンプルアプリがあります。
また、

・KitchenSink: https://github.com/appcelerator/KitchenSink

もっとも有名な、Titaniumの公式サンプルです。
Titaniumで出来る事のほぼすべてがここに詰まっていると言っても過言ではありません。
非常に充実したサンプルで、ドキュメントに書いていない要素が使われている事もあります。
実際に動かしてみたり、ソースをがんがんgrepしてみる事をオススメします。

・Titanium Mobile 1.4 API Documents: http://tidocs.com/mobile/latest/

@masuidrive の人のつくった非公式APIドキュメントです。
かなり充実してよくできており、KitchenSink をgrepしてみる前に、とりあえずここで機能があるかどうかをぱっとみてみる、という使い方には非常に向いています。

もちろん最後は KitchenSink なのですが。

・Appcelerator Titanium MobileではじめるJavaScriptによるiPhoneアプリケーション開発
http://code.google.com/p/titanium-mobile-doc-ja/wiki/toc

@donayama の人が主催しているtitanium-mobile-doc-jaプロジェクトで公開されている入門ドキュメントです。
ドキュメントとしてすごくよくまとまっており、入門には公式ドキュメントよりも向いていると思います。

自分は、いろいろいじってから存在に気づいたためお世話になれなくて(´・ω・`)ションボリでした。
最初からここをみたら、もっと学習が効率的だったかも。。。

以上、あれこれと並べ立ててきましたが、何かのお役に立てば幸いです。
まだ触ってみていない、という方はぜひ、Titaniumをいじってみるとおもしろいと思います!