[titanium]画像を使わずにボタンを表示

TitaniumMobileにはSystemButtonというのがあって、ローカルに画像を持っていなくても標準的アイコンのボタンを表示させることができる(iphone only)。

systembutton.png

でも例えば、ブラウザでよく見る左右の三角形、forward/backボタンはsystemButtonに存在しない。
デザイナさんと一緒にお仕事をしている人はいいのですけど、プログラマが適当に作ったアプリでは、こういうのを下手に描いたせいでデザインが素人っぽくなってしまったりする。著作権的なものには目をつぶって他のアプリからキャプチャするにしても、この程度の画像をいちいち切り出すのはめんどくさいよね。

そこで、文字コードを使ってそれらしい文字をtitleに指定してみた。

// Forward and back button for browser.
var buttonForward = Titanium.UI.createButton({
title:String.fromCharCode(0x25b6)
});
var buttonBack = Titanium.UI.createButton({
title:String.fromCharCode(0x25c0)
});
win.setToolbar([flexSpace,buttonBack,flexSpace,buttonStop,flexSpace,buttonReload,flexSpace,buttonForward,flexSpace]);

browserbuttons.png
お、いい感じ。もしかしてもっと色々出来るかも?

// OK. more buttons!
var buttonApple = Titanium.UI.createButton({
title:String.fromCharCode(0xf8ff)
});
var buttonCommand = Titanium.UI.createButton({
title:String.fromCharCode(0x2318)
});
var buttonOption = Titanium.UI.createButton({
title:String.fromCharCode(0x2325)
});
var buttonReturn = Titanium.UI.createButton({
title:String.fromCharCode(0x23ce)
});
var buttonForward2 = Titanium.UI.createButton({
title:String.fromCharCode(0x25c1)
});
var buttonBack2 = Titanium.UI.createButton({
title:String.fromCharCode(0x25b7)
});
var buttonNote1 = Titanium.UI.createButton({
title:String.fromCharCode(9833)
});
var buttonNote2 = Titanium.UI.createButton({
title:String.fromCharCode(9834)
});
var buttonNote3 = Titanium.UI.createButton({
title:String.fromCharCode(9835)
});
win.setToolbar([flexSpace,buttonForward2,buttonBack2,buttonApple,buttonCommand,buttonOption,buttonReturn,buttonNote1,buttonNote2,buttonNote3,flexSpace]);

morebutton1.png
もうソースコードは省略するけど、こういうのも出せる。

morebutton2.png

WEBの世界では、エンドユーザーが持っているフォントにその文字がある保証がなかったり、フォントによってどんな文字が出るか予想ができないということで、こういう文字を使うのはご法度ということになっていた。
iPhoneアプリをTitaniumMobileで作るのであれば、クライアントのフォントは常に同じだから、かなりの数の記号を使うことができる。
残念ながらtoolbarにはフォントを指定できないみたいなので、toolbarに出せる文字はヒラギノで出せる範囲に限られるみたいだけど、labelとか普通のボタンだったら、font指定することで、もっと変な記号も出せるかもしれない。

文字と文字コードの関係を調べるには、下記サイトか、Mac標準の「文字ビューア」を見ればOK.

Androidは試してないのだけれど、原理的には同じようなことができるはず。
でもAndroidの場合、インストールされているフォントが保証されないから、機種によってボタンが見えなくなったりするリスクがあるかも。

テストアプリのソースコードは下記。もちろん、画像ファイルなんて不要です:-)

[Titanium]window.urlが駄目な理由

sxchu_1276335_threads.jpg

[Titanium] window.urlは推奨されないプログラミング手法らしい – もぎゃろぐの続きです。

つい先日まで使っていた物を突然駄目だと言われても納得が出来ないと思うので、どういう場面で困ったことになるのかの例を一つ。

ある程度大きなプロジェクトになると、アクセスクラス付きのデータクラスみたいなのを作りたくなることがあります。たとえばこんなクラス。

var Obj = function(){
this.v = 1;
}
Obj.prototype.setVar = function(v){
this.v = v;
}
Obj.prototype.getVar = function(v){
return this.v;
}
var obj = new Obj();

obj.vを直接書き換える代わりに、セッターとゲッターを用意しておくことで、例えばセットした時にログを書くとか、セットする時に不正な値だったらはじくとか、いわゆるカプセル化のクラスです。
この状態で、objをあちこちの画面で共有したいとします。

var Obj = function(){
this.v = 1;
}
Obj.prototype.setVar = function(v){
this.v = v;
}
Obj.prototype.getVar = function(v){
return this.v;
}
var obj = new Obj();
var win = Ti.UI.createWindow({});
win.obj = obj;
win.obj.setVar(2);
Ti.API.debug('app.js win.obj.getXXX:'+win.obj.getVar()); // 1が帰ってきてしまう

obj.setVar(2)で値を渡したはずなのですけど、newした時の値が帰ってきてしまいました。
この例では簡単にテストするためにファイル一個でやっていますが、winの定義を別ファイルに分けても再現します。

なんでこうなるの?

Ti.UI.createWindowで生成したオブジェクトは、JavaScriptのオブジェクトみたいに振る舞っていますけど、実際にはObjectiveCやJavaのオブジェクトにリンクされています(そうじゃないとネイティブUIの部品を画面に出すことが出来ないですからね)

ここからは推測なのですけど、そうやって生成されたWindowオブジェクトのプロパティに値を代入する(たとえば、win.obj = obj; のように)と、JavaScriptとObjectiveC/Java言語の壁を越えるために、いったんJSON文字列に変換されてしまいます。

結果としてメソッドは生き残ることが出来ないので、obj.setVar()がまともに動作しなくなります。

それだったらいっそ例外になってくれれば良いと思うのですけど、中途半端に動くあたり、もしかしたらJSON化じゃなくてもう少し別の実装になっているのかもしれない。その辺はちょっと自信なしです。ソース追ってみたんだけど糸口がわかんなかった(><)

オブジェクトはコンテキストを越えられない

ちょっと底の浅い説明になってしまいましたけど、ともあれ、obj.setVar()が動作しないことは事実です。この現象は、Windowオブジェクトに限らず、Ti.Appのプロパティとして割り当てても同じ現象が起きます。要するに、Ti.のプロパティにメソッドがついたオブジェクトを引き渡してはならない、ということです。

なぜwindow.urlを使ってはいけないのか。
window.urlスタイルのプログラムを使うと、上記のようなメソッド付きのオブジェクトを共有する手段がなくなってしまうからです。

単に数値や文字列を画面間で共有したいだけであれば、windowのプロパティに渡してもいいし、相互に変更をやりとりしたければ、Titanium.App.Propertiesを使うこともできます。
このやり方で実装してしまうと、各画面がロジック部分まで持つことになるので、UIとロジックを分離することが困難になってしまいます。
UIとロジックを分離するためには、メソッドの共有がどうしても必要なのです。

公式ビデオの三本目、Building Native Mobile Applications 03 – UI Fundamentalsでは、こんなふうに説明されていました。

「真っ平らな地面」が必要な場合のみ、マルチコンテキストを使う意味がある。KitchenSinkは、たくさんのデモを見せるための物で、互いにデータを共有する必要がなかったからマルチコンテキストが使われている。

あと、ロジック部分は全部インターネット上のサーバにあって、各画面はWebAPIを叩くだけ、というアプリも、マルチコンテキストで実装しやすそうな気がします。TitaniumMobileが得意とするカタログアプリみたいなのですね。

[Titanium] window.urlは推奨されないプログラミング手法らしい

kimoi_girls.png

Titanium Mobileでは、ある程度大きなプロジェクトを作る場合、

var win = Ti.UI.createWindow({
url:'hoge.js'
});

という具合にしてurl引数を使ってソースを分割するのが半ば常識だと思っていました。

ところが実は、これってあんまり推奨されないやり方なのだそうです。
Titaniumの公式トレーニングビデオの二本目、Building Native Mobile Applicationsの10:00あたり、
JavaScriptでファイル分割をする方法について解説しているくだり。

02CrossPlatformJavaScriptApplications_1030.png


一般論だけど、1ファイル1ウインドウのプログラミングモデルはオススメしないよ。
KitchenSinkがやっているけど、あれはデモ用だから。
1ファイル1ウインドウモデルを使うと、たくさんのコンテキストを管理するために多くの問題を抱えることになる。
もちろん、ウインドウのURLプロパティを使いたくなるような場面はあるだろうけど、
一般論としては、Ti.includeやcommon.jsスタイルのrequireを使って、共通のコンテキストでウインドウを開くことをオススメする。

(トレーニングビデオは英語ですけど、単語レベルで全部書き取る自信はなかったので、だいたいこういうことを言っていたよ、という私訳です。)

プロジェクトを根底から作り直す羽目になるくらい重要な話だと思うのですけど、他に書かれているのを見た覚えがないので、トレーニングビデオは一通り見ておく必要がありそうです。
あるいは、日本語公式セミナーに参加したら説明してくれるのかもしれない。

なお、「じゃあWindow.urlを使わずにどうやって作るの?」という疑問については、

を見るのが参考になります。後者は他のテクニックもてんこ盛りで規模が大きいので、まずは前者を見るのがいいかと。
僕もあとで別のブログ記事にまとめようと思っています。


KitchenSinkでNo such file or directory: u’./Resources/comic_zine_ot.otf’

sxchu_1011518_50479789_font.jpg

今すぐフォローすべきTitanium Mobileの人達に名前を上げていただいていることに今更気づいたmogyaです。こんにちは。
ちなみに明日まで東京滞在中です。顔を見たいという奇特な方は、明日のプログラマーズカフェに遊びに来ていただけると嬉しいです。

さて、TitaniumMobileのお手本帳、appcelerator/KitchenSink。最近、コマンドラインからビルドすると、こういうエラーで止まってしまいませんか。

[INFO] Detected custom font: comic_zine_ot.otf
[ERROR] Error: Traceback (most recent call last):
File “/Library/Application Support/Titanium/mobilesdk/osx/1.6.2/iphone/builder.py”, line 948, in main shutil.copy(f,app_dir)
File “/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/shutil.py”, line 88, in copy copyfile(src, dst)
File “/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/shutil.py”, line 52, in copyfile fsrc = open(src, ‘rb’)
IOError: [Errno 2] No such file or directory: u’./Resources/comic_zine_ot.otf’

どうやら

/Library/Application\ Support/Titanium/mobilesdk/osx/1.7.1/iphone/builder.py run ./

と相対パス指定するとダメで、

/Library/Application\ Support/Titanium/mobilesdk/osx/1.7.1/iphone/builder.py run ~/develop/KitchenSink/

という具合にプロジェクトフォルダのパスを指定してあげないといけないみたい。

それとは別に、「Your TARGET_BUILD_DIR is incorrectly set. 」問題もあるんだけど、こっちの原因はわからないw
昨日Titanium meetup Tokyoで聞いたんだけど、地道にbuild/iphone/以下を削除するとか、project/cleanを試すとかしかないと言われました。
XCodeで設定をいじっても直らないわりに、一晩寝ると直ってたりするんですよね・・

[Titanium]KomodoEditでコード補完

Ti.Developers.meeting Vol 0.2で、「TitaniumStudioのエディタは微妙だからなんかいいのを教えてください。emacsとvi以外で。」と言ったらKomodo Editをオススメされた。
各種文字エンコードへの対応とか、コード補完とか拡張子による扱いの変更とか、ひと通りのことは出来るみたいなのでしばらくお試し中。

JavaScriptやRubyだけでなく、jQueryYUIの保管ライブラリまでデフォルトで用意されているあたりはさすがオープンソース。
さすがにTitaniumModileのAPIを補完するライブラリはなかったのだけれど、ぐぐったらすぐ出てきた。

javecantrell/titanium-mobile_komodo-codeintel – GitHub

ここからダウンロードしてきたファイルを適当なディレクトリに解凍して、Preference-Codeinteligenceで「Add API Catalog」してあげればOK。
Preferences.png
こんな具合に補完してくれます。Titanium.APIだけじゃなくて、Ti.APIの省略形にも対応しています。

codeintelligence.png

Titaniumの単体テスト

Ti.Developers.meeting Vol 0.2 : ATNDでお見せしたコード。

A test library for Titanium Mobile — Gist

上記をtestwin.jsという名前で保存して、こんな感じで使います。

Titanium.include('testWin.js');
addTest('とあるテストの実施ボタン',function(){
mustequal(getValue(),3,'getValue()は3を返すはず');
});

testwin.png

addTestを呼び出すたびに画面にテスト実施ボタンが追加されていきます。なので、unittestを書くような感覚で、思いついたテストをサクサクaddTestすればテスト用アプリが出来るよ、というもの。テスト結果については、mustequal(二つが一致していなかったらエラー扱いにする)とか、musttrue(変数がtrueじゃなかったらエラー扱いにする)という関数が用意されていて、結果をTi.APIのログに出力します。

ある程度大きな規模でAndroidとiPhone両方で動くものをつくろうと思ったら、データ構造とUIの分離が必要ではないかな、と考えていて。UIはもう諦めて別々につくる。データ構造については、両者共通のものが作れるはず。
で、データ構造部分を作るに当たっては、unittest的なものがほしいよね、ということで書いてみたものです。

マクドナルドで15分くらいで書いたものなので、結果出力が美しくないとか、unittestだったら自動実行してほしいとか、色々思いつく点はあるのですけど、まあとりあえず今あるものを公開してみたというスタンスです。
ライセンスは、TitaniumMobileと同じApache License, Version 2.0ということにしておくので、思うところがある方は自由にいじってみてくださいませ。僕も使っていくうちに色々直すだろうから、いいものができたらまたUpdateしようと思います。

[titanium]Basic認証を通す

sxchu_910074_mirage.jpg

TitaniumMobileでBASIC認証を通すのに、HTTPCientのsetBasicCredentialsという関数が使える、という記述を時々見かけます。guides_network_httpclient – titanium-mobile-doc-jaとか。Titanium Mobileで開発するiPhone/Androidアプリにも載っています。

でもこれ、どうも今使えないっぽい。

var xhr = Titanium.Network.createHTTPClient();
xhr.setBasicCredentials('username', 'password');
xhr.onload = function() {
Ti.API.info('xhr.onload');
};
xhr.onerror = function(e) {
Ti.API.info('xhr.onerror '+e.error);
};
xhr.open("GET","http://example.com/hoge.html");
xhr.send();

setBasicCredentialsが使えるのであれば、上記のようなコードでうまくいくはずなのですけど、1.7.1iPhoneで走らせると、

[ERROR] Script Error = invalid method '(null)' at app.js (line 2).

そんな関数は知らんぜ、と言われます。実際、Titanium.Network.HTTPClientを見ても、そんな名前の関数は存在しません。1.5位まではさかのぼってみたんだけど、その頃からなかったっぽい。

最初っから存在しなかったらWikiに書かれることもないはずなので、たぶん過去のある時点では存在したのだと思います。バージョンが変わったら関数が一個なくなるくらい、TitaniumMobileの利用者は慣れっこですよね?(涙)

なお、setBasicCredentialsが使えない状態でどうやってBASIC認証を通すかについては、Antelope Love Fan — Basic Authentication with Titanium.Network.HTTPClientが参考になりました。

var xhr = Titanium.Network.createHTTPClient();
xhr.onload = function() {
Ti.API.info('xhr.onload');
};
xhr.onerror = function(e) {
Ti.API.info('xhr.onerror '+e.error);
};
xhr.open("GET","http://example.com/hoge.html");
var authstr = 'Basic ' +Titanium.Utils.base64encode(username+':'+password);
xhr.setRequestHeader('Authorization', authstr);
xhr.send();

openしてからsetRequestHeaderを呼び出すのがポイントです。

「Titanium Mobileで開発するiPhone/Androidアプリ」


Titanium Mobileで開発するiPhone/Androidアプリ 索引

日本初のTitanium Mobile本「Titanium Mobileで開発するiPhone/Androidアプリ (Smart Mobile Developer)
」が届きました。
titanium-mobile-doc-jaを主催されている@donayamaさんの本ということで、中身を見るまでもなくAmazonで予約注文したのですけど、期待を裏切らない出来上がりです。

Titanium Mobileで開発するiPhone/Androidアプリ (Smart Mobile Developer)

Titanium Mobileで何がめんどくさいかといえば、iPhoneとAndroid両方で動くことを念頭に置くと、あっちで動くものがこっちで動かない、こっちで動くものはあっちで動かない、となることです。
Appcelerator社のエバンジェリストになった増井さんですら、「感覚的にはソースコードの7~8割を共有し、2~3割を対象のOSごとにチューニングしていくようなイメージになる」
言われているくらいなので、そもそも両方で動くものを書こうとするのが間違いなのですけど、書籍や記事のサンプルコードなどは出来れば一個ですませたいですし、分けるにしたって、どこまでが共通で動いてどこからを分けるかを考える時点でかなりめんどくさかったりするのです。

ということで、「めんどくさいからサンプルはiPhoneのみ対応、Androidについては最後に一章もうけて説明するだけ」とかでも仕方ないのかな、と思っていたのですが、中身を見ると、最初から最後までちゃんと両対応で書かれています。
もちろん、iPhoneとAndroidは別のOSなので、片方でしか動かない機能や、片方だけでやる必要のある作業もあるのですけど、それについては一目で分かるマークをつけて解説されていました。
これだけの量のサンプルを、iPhoneとAndroid両方で動くようにチェックするのは相当の手間だったはず。でも、Androidのアプリを開発したい人にしてみれば、iPhoneのおまけみたいに扱われたら残念な気持ちになってしまいますから、これはとても良いことだと思います。

Titanium Mobileで開発するiPhone/Androidアプリ「Androidのみ」

twitterアプリの章では、拙作「tm_twitter_api」を使った例も紹介されています。
つい先日Androidにも対応したのですが、書籍を書かれた時点ではまだリリースされていなかったため、書籍では、donayamaさん自身がパッチを当てたサイトへのリンクが紹介されていました。
そんな状態なのに、作者として「古川大輔氏」と名前を載せていただいていて、なんだか申し訳ないくらいです。

Titanium Mobileで開発するiPhone/Androidアプリ 古川大輔氏

おまけとして、TitaniumMobileの簡易日本語APIリファレンスもついていて、これからTitanium Mobileを触ってみる方には大変お買い得だと思いますので、まだの方はぜひどうぞ。

Ti.includeの不具合が1.7で修正される模様

aa.png

Titaniumのインクルードパスを指定する方法私案で話題にしていた、サブディレクトリ内にあるファイルがTi.includeした時どこを見に行くか問題ですが、1.7で修正が入るみたいです。

[#TIMOB-3349] iOS: Ti.include() not handling relative
pathing properly - Appcelerator JIRA

さっそく1.7RCで実験してみました。

app.js
Ti.include('lib/test.js');
lib/test.js
Ti.include('lib/a.js');
lib/a.js
Ti.API.info('I am lib/a.js');
lib/lib/a.js
Ti.API.info('I am lib/lib/a.js');

こういうプロジェクトを用意して、Titanium Mobile SDK1.62とTitanium Mobile SDK1.7RC、それぞれiOS向けにビルドして走らせてみます。

1.6.2
[INFO] Compiling JavaScript...one moment
:
[INFO] Application started
[INFO] mogyatest1/1.0 (1.6.2.878906d)
[INFO] I am lib/a.js

 1.62では、Resourceからの絶対パスでファイルを参照しています。

1.7RC
[INFO] Compiling JavaScript...one moment
:
[INFO] Application started
[INFO] mogyatest1/1.0 (1.7.0.RC1.e6afca8)
[INFO] I am lib/lib/a.js

1.7RCでは、includeしたファイルを基準とした相対パスでファイルを探すようになりました。Androidと同じ挙動です。

Resource
 └lib/test.js
 └lib/a.js

上記のような構成で、test.jsからa.jsを参照したい場合、1.6.2までは

  • iOS : Ti.include(‘lib/a.js’);
  • Android : Ti.include(‘a.js’);

と使い分けるか、Titaniumのインクルードパスを指定する方法私案で紹介したような方法で工夫する必要がありました。
1.7からは、iOSもAndroidと同じ方法でincludeが出来るようになるので、普通に書けばiOSでもAndroidでも必要なファイルをincludeすることが出来ます。

逆に、iOSしか使わないことを前提に、Ti.include(‘lib/a.js’)と書いていた方は、1.7でビルドし直すと動かなくなることが予想されます。また、僕のブログを参考にして、

var path_lib = 'lib/';
if (Ti.Platform==null || Ti.Platform.osname=='android'){
path_lib = '';
}
Ti.include(path_lib+'a.js');

と書かれていた方も、1.7では動かなくなります(iOSが正だと思っていたので)。

1.6.2、1.7共通で動くコードを書くのであれば、

var path_lib = '';
if(Titanium.Platform.osname == 'iphone' && Titanium.version<'1.7.0'){
path_lib = '/lib/';
}
Ti.include(path_lib+'a.js');

という具合にバージョン番号を見るようにする必要がありますね。

Androidのデモをする時はAndroid Screen Monitorが超オススメ

tm_twitter_apiの紹介デモで使ったら一部の方から感心していただいたのでご紹介。

プレゼンの途中でスマートフォンの画面を見せたい時、iPhoneだったらiPhoneエミュレーターで見せればいいのですけど、Andoroidの場合、エミュレーターが遅すぎるので、実質的に使い物になりません。だからといってちっちゃなスマートフォンの画面をそのまま見せたってお客さんには見えないので、結構困りますよね。

そういうときに、Android Screen Monitor がオススメです。画面をリアルタイムで PC 上に表示してくれるツールで、これを使えば実機でアプリを動かしている様子をPC経由でプロジェクタに表示することが出来ます。

Android Screen Monitor-3.png

iPhoneエミュレーターと並べてデモをすると、後ろから見ている方にはほとんど区別がつかないみたいで、昨日のプレゼンのあとも、「あの超早いAndroidエミュレーターは何ですか?」と聞かれましたw

JAVAで書かれているので、AndroidSDKを導入済みの方であれば、ダウンロードしてパスの通った位置に置いて「java -jar asm.jar 」で動くはず。公式サイトを見るとより詳しい導入方法が書かれています。