AndroidとiOS開発をTitanium Mobileでやった時に得られたTipsまとめ

半年前に仕事でTitaniumMobileを使用したので、ハマった所やTipsを幾つか紹介。
多分にオレオレプラクティスになっているので、参考程度にお願いします。

# Titanium mobileでNavigationWindowで数階層下にあるViewからRootWindowまで一気に戻る。

iOSのnavigationControllerでpopToRootViewControllerAnimatedみたいな操作が出来ないかなーと思って、色々調べたけど、標準ではAPIが用意されていないみたい。
したがって、遷移してきた画面をグローバル変数として配列で保持して、それをFor文で一気にクローズする方法を試してみました。

次の階層に遷移する際にグローバル変数の配列に画面を格納。

```javascript
var nextView = Alloy.createController('nextController').getView();
	
// 他のControllerからアクセスできるようにグローバル変数に値を格納。
// Ti.App.NavigationViews = [] を予め定義しておくこと。
Ti.App.NavigationViews.push(nextView);

// 予め、Ti.App.NavigationRootView = $.controller_name でルートビューを入れておく。 
Ti.App.NavigationRootView.openWindow(nextView);
```

Rootに戻るときは下記を記述。

```javascript
for ( i = 0; i < Ti.App.NavigationViews.length; i++) {
	closeView = Ti.App.NavigationViews.length[i];
	Ti.App.NavigationViews.length.closeWindow(closeView);
}
Ti.App.NavigationViews = [];
```

現在表示しているViewは一番最後に消すのがミソ。

# Titanium Mobileでデータベースを参照したい（iOS version）
SQLiteのクライアントソフトは「Lita」がおすすめ。

- Lita
http://www.dehats.com/drupal/?q=node/58

## 手順
1. Titanium stadio で iOSシュミレータ立ち上げ

2. Litaを起動。

3. 「DBFile」でビルドしたアプリのsqlファイルまで移動。
ちなみにAlloyで作成した場合のファイルパスはデフォルトで
```
~/Library/Application Support/iPhone Simulator/<iOSのバージョン>/Applications/<アプリのGUID>/Library/Private Documents/_alloy_.sql
```
にあります。

4. 「open」を押下。

これで見られます。

# Titanium Mobileでトランザクション(trunsaction)。

AlloyでトランザクションをやってくれるAPI等はまだ提供されてないみたい。
なので、下記な感じで書きましょう。

```javascript
var db = Ti.Database.open('_alloy_');
db.execute('BEGIN TRANSACTION');

db.execute(String.format("insert into table_name (title) values ('%s')", "title_name"));
// Alloy.createModel(ほげほげ).save() みたいなことやってもトランザクションの対象にはなりません。

db.execute('COMMIT;');
db.close();
```

【問題点】
Alloy使ってる人は、上記でinsertしたレコードにはalloy_idが入らないので、注意してください。

# Titanium Mobile にて 特定のテーブルのレコードを全消去する

Truncateがなかったので、パフォーマンスがアレですが、下記でテーブルのレコードが全部、消せます。

```javascript
var collection = Alloy.createCollection('model_name');
collection.fetch();
while (collection.length) {
	collection.at(0).destroy();
}
```

ここを参考にしました。

https://developer.appcelerator.com/question/148160/destroy-model-in-alloy-doesnt-seem-to-be-working

# Titanium Mobile（Alloy）でModelにID属性を定義したい
configあたりで下記のように書くと出来ました。

```javascript
exports.definition = {
	config: {
		columns: {
		    // 制約をここに書く。
		    "id": "integer PRIMARY KEY AUTOINCREMENT",
		    "column001": "text",
		    "column002": "text"
		},
		adapter: {
			type: "sql",
			collection_name: "AnswerRecord",
			idAttribute: 'id'  // 「id」がID属性であることを宣言。
		},

	},

       // 以下、モデルの定義

```

# Titanium Mobile で整形した日付を取得する

SQLiteではデータ型が存在しないので、text型で保存するのが定番らしい。
Alloyには日付操作ライブラリ「moment.js」が提供されているので、それを使って、text型の日付が取得できる。

```javascript
var moment = require('alloy/moment');
var now = moment() // これで現在日付が取得可能。
var foematedNow = now.format("YYYY-MM-DD HH:mm:ss");

console.log(foematedNow); // 2014-01-21 20:01:87
```

SQLiteの関数 date(), time(), datetime() の書式に合わせるのが、良いらしいので、上記ではdatetime()の書式に合わせて、記述しました。
これでソートもうまく出来るそうな。（未検証）

【参考にしたサイト】
Alloyの公式ドキュメントによるmoment.jsの説明
http://docs.appcelerator.com/titanium/latest/#!/api/Alloy.builtins.moment

moment.jsの公式サイト
http://momentjs.com/

# Titanium Mobile で navigation window にて「戻る」ボタンを非表示

```javascript
// ウィンドウオブジェクトのidがwindow
$.window.leftNavButton = Ti.UI.createLabel({text:' '});
```

カッコ悪いけど、これが一般的っぽい。

# Titanium Mobileでtsvファイルから初期データを入れる。

tsvファイルから初期データ読み取って、テーブルに入れる方法を考えた。

■ tsvファイル

|1行目|column1|column2|column3|
|:------------:|:------------:|:------------:|:------------:|
|2行目|aaa|AAA|111|
|3行目|bbb|BBB|222|
|4行目|ccc|CCC|333|
|5行目|ddd|DDD|444|

```javascript
/**
 * tsvファイルからインスタンスを作成し、データ作成。
 * １行目はテーブルのカラム名を記述。
 * ２行目以降は１行目の同列のカラムのデータを記述。
 * @param {Object} filePath tsvファイルまでのpath
 * @param {Object} modelName モデル名
 */
function insertInititalData(filePath, modelName) {
	var fileData = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, filePath);
	var fileContents = fileData.read().toString();
	var fileLines = fileContents.split(/\r\n|\r|\n/);

	// カラムを取得
	var tableColumns = fileLines[0].split('\t');

	// ２行目以降のデータでインスタンス化
	for (var i = 1; i < fileLines.length; i++) {
		var modelLineWords = fileLines[i].split('\t');
		var params = _.object(tableColumns, modelLineWords);
		var model = Alloy.createModel(modelName, params);
		model.save();
	}
}
```

ファイルの末尾に空行が入ってるとうまく動かないと思うので、そこだけ除去してください。

上記の方法の利点はカラムが増えても、改修不要な点。
JavaScriptもさることながら、Underscore.jsがかなり使える。
Ruby on Rails 使ってる人はすぐに慣れるはず。

# Titanium Mobile で定数管理

Alloyで定数を管理するファイルを作成して、グローバル変数で読み取る方法を考えた。

■ 定数ファイル (app/lib/constants.js)

```javascript
module.exports = {
	config : {
		code001 : "XXXXXXXXXX",
		code002 : "YYYYYYYYYY"
	}
};
```

■ alloy.js

```javascript
Alloy.Globals.constants = require("constants");
```

■ index.js

```javascript
console.log(Alloy.Globals.constants.config.code001); // XXXXXXXXXX
```

2014-02-01 追記
module.exports を exportsにしてしまうと、Androidでは動きませんので注意。


# Titanium Mobileでナビゲーションバーの半透明を無くす方法

class = "container"として、

```css
".container" : {
 	barColor : "green",
 	translucent : false
}
```

# Titanium Mobileでrequireを使用して、モジュールを読み込む方法（Android/iOS）

定数管理したJSファイルを読み込もうとしたんだが、

constants.js (app/lib/constants.js) 

```javascript
exports = {
	test : {
		name : "hogehoge"
	}
}
```

alloy.js

```javascript
Alloy.Globals.constants = require("constants");
console.log(Alloy.Globals.constants.test.name); // point1
```

これだと、point1でiPhoneでは、値が出力されるが
何故かAndroidだと動かない。
(constantsが{}になっていて、constants.test.nameがundefinedになる。)

これを下記のように直したら、動いた。

constants.js (app/lib/constants.js)

```javascript
module.exports = {
	test : {
		name : "hogehoge"
	}
}
```

CommonJSやmodule.exportsやexportsがまだ、そこまで理解できてないので、時間を見つけて、調べる予定。



# Titanium Mobile でOSのバージョンを取得する。（iOS/Android）

Titanium MobileでOSのバージョンを数値で取得(Android 2.3.3で試す場合)

```javascript
var version = parseInt(Ti.Platform.version.slice(0,2), 10);
console.log(version) // 2.3
```
OSのバージョンは「4.0.4」とか「2.3.3」と言った表記なので、
そのまま数値にすると「NaN」になってしまう。

したがって、「4.0」や「2.3」などに整形する必要がある。

# Titanium MobileにてBackボタンを無効化する。(Android/iOS)

両OSでBackを無効化したかったので調べた。
色んな所で使用したかったため、グローバル関数にしてみた。

alloy.js

```javascript
Alloy.Globals.disableBack = function(windowObj) {
	if (OS_IOS) {
		// iOSはナビゲーションバーを使用している前提。
		windowObj.leftNavButton = Ti.UI.createLabel({
			text : ' '
		});
	} else if (OS_ANDROID) {
		windowObj.addEventListener('android:back', function() {
			// 何もしない。
		});
	}
};
```

index.js

```javascript
Alloy.Globals.disableBack($.index) // windowオブジェクトを引数にする。
```

これで、動きました。

# Titanium MobileのSQLite3でBooleanを使用する際の注意点（Android/iOS）

SQLite3のカラム型でBoolean型は存在しないので、代わりにInteger型を使用することになると思うのですが、iPhoneだとtrueを設定しても、SQLiteでは自動的に「1」が登録されます。
しかし、Androidでは自動的に変換は行われず、nullになってしまうので、注意が必要です。
素直にtrueなら1、falseなら0を使うようにしましょう。

# Titanium MobileでBase64でエンコードされた画像を表示。（Android/iPhone）

サーバーからBase64でエンコードされた画像データを表示したかったのだが、AndroidとiPhoneでハマったので、メモ。

```javascript

// 画像取得先のURL（仮のもの）
var url = "http://hogehoge.com/getImage";

var xhr = Ti.Network.createHTTPClient({
	onload : function(e) {
		// レスポンスは{"img_string" : "Base64でエンコードされた文字列"}で返ってくるものとする。
		var response = JSON.parse(this.responseText);
		var img = Ti.UI.createImageView({
			image : Ti.Utils.base64decode(response["img_string"]),
			width : Ti.UI.FILL
		});

		// 以下、imgを画面に表示する処理を書く。

	},
	onerror : function(e) {
			// 失敗した時の処理
		},
		timeout : 3000 // 3秒経過したら、タイムアウト
	});
xhr.open("GET", url);
xhr.send();
```

ここで注意したいのは、最初、createViewのbackgroundImageで表示しようと思ってたのだが、何故かandroidでは表示できなかった。

```javascript

Ti.UI.createView({
	backgroundImage : Ti.Utils.base64decode(response["img_string"]),
	width : Ti.UI.FILL
});
```

仕方ないので、createImageViewでやったら、AndroidとiPhoneの両方でうまくいったので参考までに。

- ImageVIewのimage
http://docs.appcelerator.com/titanium/3.0/#!/api/Titanium.UI.ImageView-property-image

- ViewのbackgroundImage
http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.UI.View-property-backgroundImage

リファレンスはしっかり見ないと（反省）

# Titanium Mobileで親Viewにイベント伝搬させない

親Viewと子Viewに同じClickイベントを設置した時、デフォルトでは子ViewのClickイベント後、親Viewのclickイベントが発生します。

```javascript

var parentView = Ti.UI.createView({
	height : Ti.UI.FILL,
	width : Ti.UI.FILL
});

parentView.addEventListener('click', function(e){
	console.log("parent click!");	
});

var childView = Ti.UI.createView({
	height : Ti.UI.FILL,
	width : Ti.UI.FILL
});

childView.addEventListener('click', function(e){
	console.log("child click!");	
});

parentView.add(childView);
```

click時

```bash
child click!
parent click!
```

これを制御するには、bubbleParentプロパティを制御します。

```javascript

var childView = Ti.UI.createView({
	height : Ti.UI.FILL,
	width : Ti.UI.FILL,
	bubbleParent : false
});
```

click時

```bash
child click!
```

これで親プロパティのイベントが発生させないようにできます。


# Titanium MobileでAlloyでアプリ名を日本語にする

- プロジェクトのappフォルダと同階層に「i18n」フォルダを作成
- i18n/ja/app.xmlを作成。
- app.xmlを編集

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<resources>
	<string name="appname">日本語アプリ名</string>
</resources>
```

これで、ビルドするとiOSとAndroidの両方でアプリ名が日本語になります。

# Titanium MobileのiOS用スプラッシュ画像はちゃんとしたpng拡張子を使おう

お前は何を言って(略

Titanium MobileでiOS用のスプラッシュ画像を使うためにgifファイル用意してたんだが、pngしか設定できないようなので、拡張子をリネームして、下記のファイルを設定した。

- Default.png
- Default-568h@2x.png
- Default@2x.png

iOS Simulatorは指定したインチに沿って、使用するスプラッシュ画像を選択してくれてたんだけど、
いざ、実機で確認すると「Default-568h」を使用すべきところを「Default」しか使ってくれず、
そのおかげで、アプリ自体、640 × 960 サイズになって、上下に黒い部分の領域ができてしまっていて、この解消方法がわからなかった。

解消方法は素直に gif → png にちゃんと画像変換すれば、設定出来ました。

今考えると、拡張子リネームしただけで、変換できるか！！って言われそうだけど、
寝てなかったから頭が回ってなかったんです。（言い訳）

# AlloyのControllerで作成したViewにtssの定義を適応させる。
Controllerで作成したViewはそのままだとtssで定義したclassを当てはめることができないので、Dynamic Styleを使うと良い。

index.tss

```css
// Button 全てに以下の定義が適用。
"Button" : {
	backgroundColor : "black"
}
".buttonEnabled" : {
	opacity : 1.0,
	touchEnabled : true
}
".buttonSize" : {
	width : "50dp",
	height : "50dp" 
}
```

index.js

```javascript

var button = Ti.UI.createButton();
var style = $.createStyle({
	apiName : "Button" // tssの「Button」を指定。
	classes : ["buttonEnabled", "buttonSize"] // 複数のClassを指定。
}); 

button.applyProperties(style); // tssのクラスが適用される。
```

【参考URL】
http://docs.appcelerator.com/titanium/3.0/#!/guide/Dynamic_Styles

# Alloyで画面下部に広告スペース載せるときのレイアウト見本
画面下部に高さ50の広告エリアを表示する際の指定方法が結構、ハマったので書いておく。

Viewファイル

```xml
<Alloy>
	<Window>
		<View class="notAdViewArea">
		</View>
		<View class="adViewArea">
		</View>
	</Window>
</Alloy>
```

Tssファイル

```css
".notAdViewArea" : {
	backgroundColor:"green",
	bottom:"50dp"
	
}
".adViewArea" : {
	backgroundColor:"yellow",
	height:"50dp",
	bottom:"0dp"
}
```

以上