T

SwiftとTitanium

Objective-Cと並んでiOS用アプリの開発に利用できる言語、Swiftが発表されました。私たち歌と踊りが大好きな陽気で愉快なTitaniumユーザーとしても注目すべきニュースです。

Titaniumとは、ご存知の通り、アプリの上にJavaScript実行環境を用意して、JavaScriptを通じてアプリを実際に動かすネイティブのAPIを叩いてしまうツールです、というとわかりにくいですが、ようするにネイティブのAPIで動作するアプリがJavaScriptで書けてしまう素敵なツールです。インストールガイドを辿るくらいの手間をかけるだけで簡単にセットアップできる上に、Node.jsなどモダンなJavaScriptの知識を活かして大規模なプログラムを作成することも可能です。ややこしい設定もなくiOSやAndroidなどに対応し、慣れたユーザーなら複数のプラットフォームで動作するアプリを短期間で開発することができます。

さて、Titaniumはこれまで、JavaScriptで書かれたプログラムをアプリの上で動作するJavaScript実行環境が評価して、その結果をネイティブな環境とやり取りする仕組みで動作するものでした。しかし、現在開発中の次期バージョン、Ti.Nextと呼ばれている新しいシステムでは、JavaScriptに独自拡張を施した言語で記述するとそれらをObjective-CやJavaといった言語にコンパイルして、文字通りネイティブアプリにしてしまう仕組みが導入される予定です。本当はJavaScriptがそのままObjective-Cに変換できればいいのでしょうが、さすがにちょっと記述するのが難しいので独自の変更が入っています。おそらく、現在JavaScriptとXMLでTitaniumのアプリを作成するAlloyフレームワークがあるので、ユーザーは新しい変な言語を覚えるのではなく、これまで通りAlloyでアプリを作成すれば、その他のツールチェインがよろしく取りはからってくれるよう、いわばAlloyがDSLとして機能するようになると予想されます。Alloy自体はBackbone.jsのModelやCollectionを取り入れたBackbone.js + Underscore.jsみたいな最近よくあるJavaScriptのMVCフレームワークに似たところもあるので、慣れてしまえば結構便利です。

そこへ話題のSwiftの登場となるわけですが、RubyMotionのような開発環境を提供しているサードパーディーのコミュニティは割と早めに反応しているようなので、TitaniumとSwiftのこれからについても、簡単な展望を予想してみたいと思います。このSwift、ざっと眺めたところJavaScriptとかなりよく似ています。少なくともObjective-Cの文法に追随するためのヘンテコな仕組みを必要とせずJavaScriptからのコンパイラも作成しやすいように見受けられます。そのため、特にTi.Nextのような仕組みはiOSに関しては飛躍的にTitaniumに有利になるでしょう。Swiftを覚えなくても馴染みのあるJavaScriptで記述できる上に、単純な仕組みのコンパイル部分を理解すればいろいろな応用が出来るはずです。TitaniumはバックエンドはAndroidやTizenなどプラットフォーム毎に切り離されているので、Titanium自体の開発者にとってはそれぞれに合わせてネイティブの言語でAPI層を構築するのが大変でしたが、その手間がかなり軽減されそうです。そして、それはJavaScriptでクロスプラットフォームのアプリを作成する利用者にとっても大きなメリットになるでしょう。

Titaniumの使い方として、まだObjective-CやAndroidのJavaにあまり精通していない人でも、とりあえず動作するものが作れてしまう利点を最大限に利用して、動くものを作りながら、徐々にプラットフォーム毎の仕組みを理解し、やがてはそれぞれのネイティブな言語での開発を始めるに至るブリッジの役割を求めるケースが最近増えてきているように思われます。そんな意味では、Swiftを活用するのはいったんエキスパートに任せて、JavaScriptからSwiftを利用しつつ徐々に学んでいくのも悪くないと思います。なんといっても動く製品がなければビジネスも何も始められませんからね。

そうそう、METALが発表されましたが、このあたりはLanicaの人たちに頑張ってほしいですね。

2013年のTiIconicFont + Alloy

ウェブ上の情報が少し古くなっていたようでいくつか質問があったので、現時点で動作するサンプルをまとめておきます。あすとろなんだっけかさんの『Titanium mobile “early” Advent Calendar 2012』の18日目、@hoyo1111 さんのエントリー『【18日目】AlloyでもTiIconicFontを使いたい』を参考にしています。というかこれをほぼまるまる使っています。書き方を入門者に対応しただけですね。

TiIconicFontはサイレントヒルのひげ怪人Titaniumユーザー会ではおなじみのk0sukeyさんが公開してくれている、Font AwesomeやLigature Symbols、SS PikaのようなウェブフォントをTitaniumのLabelでも利用できるようにするモジュールです。インストール方法は次の通りです:

(1)ダウンロード

Alloyのプロジェクトはもう作成しましたね?まだなら、先に作成しておいてください。

TiIconicFontはGithubのページからZIP形式でダウンロードしたりgitでcloneして入手します。

(2)展開

Zipで落とした人はTiIconicFont-masterとかいう名前のディレクトリの下のResources/lib、cloneした人はTiIconicFont/Resouces/libを自分のAlloyのプロジェクトのapp/assets/以下にコピーします。app/assets/libディレクトリが作成されていることを確認しましょう。

(3)フォントデータの用意

TiIconicFontはそれだけでは動作しません。フォントのデータ(ttf)ファイルが必要です。例えばFont Awesomeならプロジェクトのウェブサイトからダウンロードして、展開したディレクトリのfont/fontawesome-webfont.ttfをapp/assets/fonts/以下にコピーします。app/assets/fontsディレクトリは存在しないので作成してからコピーしましょう。

app/assets以下にこれらが用意されればOKです。

app/assets/
├── fonts
│   └── fontawesome-webfont.ttf
└── lib
    ├── FontAwesome-deprecate.js
    ├── FontAwesome.js
    ├── IconicFont.js
    ├── LigatureSymbols-deprecate.js
    ├── LigatureSymbols.js
    └── ti.ss-pika.js

(4)iOS用の設定

iOSの場合は必要なファイルを生成するためにいったんビルドします。ビルドして作成されるbuild/iphone/Info.plistをプロジェクトディレクトリの直下にコピーして、コピーした方のファイルの最下部(/dictの上)に以下を追記します。Androidの場合は無視して地球の未来のことを考えます。

	<key>UIAppFonts</key>
        <array>
          <string>/fonts/fontawesome-webfont.ttf</string>
          <string>/fonts/LigatureSymbols.ttf</string>
          <string>/fonts/ss-pika.ttf</string>
        </array>

(5)動作試験用のViewを用意

app/views/index.xmlに下のような記述を用意します。

  <Label id="symbol" />

app/styles/index.tssにも適当な値を設定しておきましょうか。

"Label": {
  width: Ti.UI.SIZE,
  height: Ti.UI.SIZE,
  color: "Black"
}

(6)動作試験用のcontrollerを用意

app/controllers/index.jsに次のように記述します。

// Font Awesome
var fontawesome = require('lib/IconicFont').IconicFont({font: 'lib/FontAwesome'});
$.symbol.setFont({fontSize: 32, fontFamily: fontawesome.fontfamily()});
$.symbol.setText(fontawesome.icon('icon-thumbs-up'));

$.index.open();

Font Awesomeで利用できるアイコンの一覧はlib/FontAwesome.jsにあります。

(7)自慢

awesome_android

TiTwilio

TitaniumでTwilioを簡単に扱うためのモジュール、TiTwilioを公開しましたので、使い方を説明します。Android、iOS共通です。

最初にTwilioのアカウントを作成します。電話番号を取得したら、次にアプリを登録します。自分が用意する認証サーバとTwilioを連携させるために必要なので、必ず登録してください。

tw1

「TwiMLAppを作成する」ボタンから登録することができます。

大事なのは、Request URLのところです。認証が始まってCapability Tokenを取得した端末は、このRequest URLにPOSTリクエストを発行します。このリクエストへのレスポンスでアプリの挙動が決まります。
tw

TiTwilioでは、リダイレクト時にサーバに指定されたパラメータを追加する機能があります。Titaniumで作成したアプリと認証サーバ間との連携に利用します。実際のコードで説明します。

var TiTwilio = require('org.selfkleptomaniac.mod.titwilio');

// call
TiTwilio.connect({
  url: 'http://your-auth-server.example.com', // auth server (required)
  params: {key: value, post: data} // post data (optional)
});

これだけのコードで上の図の一連の動作が実行されます。paramsに指定した値がPOSTリクエストのキーと値としてRequest URLに送信されます。従って、サーバ側ではこのparamsによってアプリ同士の通話や携帯・固定電話への通話、自動返信などの挙動を変えることができます。

githubのレポジトリにはsinatraで動くRubyのサーバ側スクリプトがserverディレクトリにあります。こちらではRequest URLに「/call-to-me」で終わるURLを指定するようになっています。POSTで送信するtypeの値によってアプリ間通信や携帯・固定電話への通話など機能が分離しています。

アプリ間の通信や携帯・固定電話からの通話を受け付けるためには、認証サーバにログインしている必要があります。Androidの場合はPendingIntentに受信時に起動するサービスを登録しておきます。

var TiTwilio = require('org.selfkleptomaniac.mod.titwilio');

var pendingIntent;
if(Ti.Platform.osname === 'android'){
  var intent = Ti.Android.createServiceIntent({url:'service.js', twilio: TiTwilio});
  pendingIntent = Ti.Android.createPendingIntent({intent: intent});
}

// login
TiTwilio.login({
  url: 'http://your-auth-server.example.com', // auth server (required)
  params: {key: value, post: data}, // post data (optional)
  pendingIntent: pendingIntent
});

これで受信の準備が完了します。iOSの場合はincomingCallイベントをTiTwilioオブジェクトに追加するだけで受信することができます。

Androidの場合は、Resources/android/以下にservice.jsを用意します。通話を受信するたびにこのサービスが起動しますので、ダイアログを出したりするなど受信時の動作をアプリケーションレベルのイベントに登録しておいて、それをこちらからfireEventで呼び出します。

try{
  var service = Titanium.Android.currentService;
  var intent = service.intent;

  Ti.App.fireEvent('inComingCall', {intent: intent});
  service.stop();
}catch(e){
  Ti.API.info(e);
}
service.stop();

サービスの詳細についてはこちらをご覧ください。ただし、intervalで登録しても意味がないので、tiapp.xmlは

<services>
  <service url="service.js" />
</services>

のように記述します。また、Androidのパーミッションの設定も忘れないようにしてください。以下2行を追記する必要があります。

  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

iOS実機上でのデバッグ

Titanium SDK 3.0がリリースされたので、さっそく新機能であるリモートデバッグについて調べてみました。

原文:http://docs.appcelerator.com/titanium/latest/#!/guide/Debugging_on_iOS_Devices

====================================================

iOSの実機上でのデバッグ

【概要】

Titanium SDK 3.0から、Titanium Studioを利用してiOS実機上でデバッグセッションを開始することができるようになりました。iOS実機でのデバッグは以下のステップで実行できます:

  • 「Debug on iOS Device」を選択する
  • Stuidoでアプリをビルドする
  • アプリを自動または手動で実機に同期する
  • アプリを実機上で起動する
  • LANのWifiを経由してStudioとデバッグセッションを開始する

本項ではこれらのステップとデバッグセッションの起動について解説します。

* 「Deploying to iOS devices」に記載されていますが、iOS実機上でのデバッグはOS Xでのみサポートされています。

【必要なもの】

iOSの実機でデバッグするためには

  • Titanium StudioからiOS実機へのアプリのインストールが出来るようになっている必要があります(まだアプリを実機にインストールしたことがない方は「Deploying to iOS devices」をご覧下さい)。
  • Titanium SDK Release 3.0以降がインストールされていること。
  • Titanium Studio 3.0以降がインストールされていること。
  • OS Xマシーンと実機が同じWi-Fiネットワークに接続されていること。デバッガープロトコルはネットワーク越しに送信されます(ケーブルで代用することはできません)。

デバッガーを実行するたびにStudioがアプリをビルドしてiTunes経由でインストールしようとします。iTunesを経由してインストールするのではなく、必ず

  • USBケーブルで実機を接続します(推奨)。
  • 実機側でLAN経由でiTunesと同期するよう設定します。そのためには実機は必ず電源と接続されている必要があります。それからルーター側でマルチキャストとUPnPが有効になっていなければいけません。

iTunesを実機と同期させている場合は、iTunes側で自動的にアプリを同期する設定にすると便利です。

iTunesで同期する以外のやり方として、Titaniumでアプリのビルドが完了したらXcodeやiPhone Configuration Utilityを使ってアプリをインストールすることもできます。

【ネットワーク設定】

iOS実機上でデバッグするためには、Studioを実行しているコンピュータと実機を同じWi-Fiネットワークに接続する必要があります。
公衆無線LANの中にはインターネット側の通信だけを許可してクライアント同士の通信を許可していない設定になっているものもあります。このようなネットワークではiOSのデバイス上でのデバッグを実行することはできません。
もし適当なネットワーク環境がない場合は、ad-hocネットワークをコンピュータ上で作成してデバイスからそちらを利用する方法があります。詳しくはAppleサポートサイトの「OS X Mountain Lion: Create a computer-to-computer network」からad-hocネットワークの作成方法をご覧ください。

【デバッグセッションの起動】

デバッグを開始する前に、iPhone/iPadをターゲットにしたTitaniumのプロジェクトを作成しておく必要があります。基本的には次の3つのステップがあります:

  1. Titanium Studioでデバッグ用セッションを初期化する
  2. アプリケーションのビルドとインストールが完了するのを待つ
  3. 実機でアプリを実行してデバッグを開始する

・デバッグ用セッションの開始

  1. Titanium StudioのApp ExplorerまたはProject Explorerビューからプロジェクトを選択
  2. ツールバーのDebugアイコンをクリック
  3. iOS Deviceを選択してDebug on iOS Deviceウィザードを開始
  4. ウィザードを完了してデバッグ用セッションを初期化。Titanium Studioでデバッグ用のビルドで利用する証明書やプロヴィジョニングプロファイル、SDKのバージョンを選択するプロンプト画面が表示される

一度ウィザードを完了するとTitanium Studioは起動設定を作成し、Titanium iOS Device – <アプリ名>として保存します。この起動設定をDebugのドロップダウンメニューから選択するとウィザードをスキップして同じ設定の新しいデバッグ用セッションを開始することができます。

【ビルドとインストール】

デバッグ用セッションの開始前にいくつかステップがあります。

  1. Titanium Studioでプロジェクトを作成する
  2. ビルドしたアプリはiTunesにインストールされる。すでに実機にインストール済みであれば、iTunesは古いバージョンと新しいバージョンを置き換えるかプロンプトを表示する。
  3. 接続されたデバイスにiTunesで同期する

それ以外にも、XcodeやiPhone Configuration Utilityを使って新しくビルドしたアプリを実機にインストールすることもできます。

【アプリの起動とデバッグ】

アプリのインストールが完了すると:

  1. 実機上でアプリを実行
  • ダイアログが表示され、デバッグ用セッションが開始するのを待っていると表示される
  • Titanium Studioに実機が接続されてデバッグ用セッションが開始(して、Titanium StudioのDebugパースペクティブが表示される)
  • 同じデバッグバージョンのアプリを同時に複数の端末にインストールして、同時に起動するなど稀なケースでTitanium Studioからどちらのバージョンでデバッグするかプロンプトで表示されることがあります。

    【iOS実機デバッグでの問題トラブルシューティング】

    iOSの実機がDebugのドロップダウンメニューに表示されません

     tiapp.xmlで選択されているTitanium SDKが3.0以降であることを確認してください。

    最初の起動時にiTunesから実機へのアプリの同期が失敗します

     iTunesのAppsタブを確認してください。自動で同期する設定になっていること、新しいアプリの隣に表示されているチェックボックスがチェックされているかどうかを確認してください。されていなければチェックして「同期」をクリックしてください。

    デバッガの接続に失敗します

     iOS実機とコンピューターが同じWi-Fiネットワーク上にいることを確認してください。
     Titanium Studioで新しいデバッグ用セッションを起動してください。実機上のアプリをリスタートしないとデバッグ用セッショッンもリスタートすることができません。

    Wi-Fi同期での問題

    USDBケーブルで接続されていなくてもiTunesから実機が確認できる状態になっている必要があります。そうなっていない場合は以下を試してください:

    Wi-Fiネットワークにマルチキャストまたはブロードキャストアドレスがあるかどうかを確認してください。たいていの家庭用ルーターではデフォルトで有効になっているはずですが、企業内のLANや公衆無線LANではそうなっていないかもしれません。マルチキャストまたはブロードキャストアドレスはWi-Fi同期では必須になっていますが、デバッグ目的であればUSBケーブルで同期している場合は不要です。
    Appleサポートの「iTunes 10.5 and later: Troubleshooting iTunes Wi-Fi Syncing」に記載された説明に従ってください。

    【ネットワーク接続のテスト】

    実機とコンピューターが同じネットワークにいるかどうかを確認するために、Titanium Studioで試験用のウェブサイトを作成します。

    実機のIPアドレスを確認します(こちらの解説を参照: http://www.iclarified.com/entry/index.php?enid=496)。
    Titanium Studioで新規プロジェクトを作成します。
     File > New > Projectをクリック
     Web Projectを選択してNextをクリック
     Default Templateを選択してNextをクリック
     プロジェクト名を入力してFinishをクリック
    HTMLファイルをウェブプロジェクトに作成します:
     Project Explorerビューでプロジェクトのフォルダを選択してFile > Newをクリック
     ファイル名にtest.htmlを指定
     新規ファイルを編集して何か基本的なHTMLを入力
    Preferences > Titanium Studio > Web Servers > Built-inをクリック
    実機のIPアドレスと同じレンジにあるIPアドレスを選択
    Titanium Studioを再起動
    Project Explorerビューで先ほど作成したHTMLファイルをクリック
    Run > Runをクリック
    ブラウザが起動してウェブページを開きます
    iOS実機上でウェブブラウザを開く
    コンピューター上のURLバーに表示されたURLを実機側でも開く
     iOS実機上でもページが表示されれば、同じネットワークにいます
     ページが表示されなければネットワークに問題があります

    【関連トピック】

    badgeの限界を試す

    こちらは「Titanium mobile “early” Advent Calendar 2012 – あすとろなんとかさんを救え –」の三日目用のエントリです。
    ========================================
    Titanium Mobileに限った話ではないのですが。

    Ti.UI.createTabで作ったtabのbadgeって何桁まで表示できるかご存知ですか?

    正解は、10億からあふれたので999,999,999まででした。良い子のみんな、未読メールを10億通も溜め込んじゃダメだぞ!

    …くだらない、だと?

    でもまだやります。じゃあ、Ti.UI.iPhone.appBadgeの方はどうでしょうか?

    ふっふっふ。こちらの方は意外と軟弱で、たったの10,000でもうこんなみっともない姿に。

    わっはっは。は。

    小ネタでいいっていったじゃないか。

    じゃあ、Ti.UI.SIZEとTi.UI.FILLに全て置き換えられたかにみえる幅や高さの自動指定「’auto’」がまだ現役で残っている、それどころかSIZEやFILLだとエラーになるメソッドはUIのどのAPIでしょうか?というとっても有用な小ネタはあすとろなんとかさんに譲ります。さらば。ばっははーい。

    起動しないiPhone Simulatorをなんとかする

    こちらは「Titanium mobile “early” Advent Calendar 2012 – あすとろなんとかさんを救え –」の三日目用のエントリです。小ネタ限定ということで、内容は薄いですがまあ一発披露します。とはいえ、ちょっと危険なことも含まれているので、どうか実行する際はよくよく考えてくださいね。実際、失敗してしまった例を見たことがありますが、同情することしかできませんでした。

    ——————————————–

    専門学校で教えているときなど、よくiPhone Simulatorが起動しないという報告を受けることがあります。実際、プログラムはどこも間違っていないのに、シミュレータを起動したらアプリが開始される間もなくドンと落ちます。ちょうど初日のあすとろなんとかさんのエントリと同じように、特にエラーも吐きません。

    エラーも吐かないのではっきりとした原因はわかりませんが、どうやらシミュレータのゴミが溜まっているとこのようなことが起きるようです。ゴミといっても別に不快な排泄物とかではなく、アプリのGUID毎のディレクトリが残ったままだと発生することがあることまではわかりました。なので、これを掃除すればなんとかなります。もし、インストールはしたのに全然先に進めずお困りの方は下のやり方を試してみてください。

    では掃除しましょう。最近のOS XはユーザーのライブラリディレクトリがFinderからは見えないようになっているので、この操作はターミナルから実行する必要があります。GUIしかわかんなくてマウスがなけりゃ何にもできないヘタレコマンドラインの操作に慣れていない方にはちょっとおっかないかもしれませんが、その場合はこちらをみて心の準備をしてください。下の「$」ドル記号はターミナルが出力する文字なので、みなさんはその右側から入力します。

    $ cd $HOME/Library/Application\ Support/iPhone\ Simulator/6.0/Applications/
    $ ls
    0864BE05-B166-46C8-8BCC-32AEC2245170/   27706EFB-BC84-4EA6-912E-8D970ECEC458/   E9BAE0E2-96DE-4B82-8F88-3204B09E7E92/
    20ED5860-D7A5-4D3F-9B7B-EE982212D76A/   4F04718F-723B-4CD7-9834-BAE19621D576/
    

    cdはchange directoryでディレクトリを移動するコマンドです。右側は異動先です。$HOMEはいつでもどこでも誰とでもあなたのホームディレクトリに置き換わってくれる魔法の言葉です。右端まで入力したらreturnキーを押しましょう。何もいわれなかったら大丈夫です。lsはlistのことで、ファイルの一覧を表示します。lsの後すぐにreturnキーを押すと、例えば上のようになんかだらだらと文字が表示されることがあります。

    実はこちらがiPhone Simulatorにインストールして起動したアプリたちのGUIDなんです。アプリがシミュレータの中にある場合はこれらも残るようなので、いったんばっさり削除しましょう。

    $ pwd
    /Users/あなたのログインユーザー名/Library/Application Support/iPhone Simulator/6.0/Applications
    $ rm -rf $HOME/Library/Application\ Support/iPhone\ Simulator/6.0/Applications/*
    $ ls
    

    pwdというのは現在あなたがみているディレクトリを返すコマンドです。出力結果が上と同じようになっていることを確認してください。しましたね?しましたよね?次のコマンドは、これはファイルをremoveするrmコマンドです。基本的に後戻りはできませんので、間違ってファイルを消してしまうとまあ元に戻すのはとっても大変だし普通は無理だと考えていただいて結構です。なので、これを実行する場合は相対パスではなくちゃんと絶対パスを使って慎重な上にも慎重になった方が身のためです。思い出の詰まった大事なファイルを誤って消してしまうのが嫌なら、悪いことはいわないのでpwdで出力された内容をコピペしてください。もっとも、「Application Support」や「iPhone Simulator」のようにスペースが入る場合、これはコマンドの引数の切れ目なのかディレクトリの中の空白文字なのかシステム側で自動的に判別できないため、ターミナルでディレクトリの中の空白文字には直前にバックスラッシュを入れて空白であることを明示する必要があることをお忘れなく。最後のlsコマンドで何も返ってこなければ、作業は完了です。Titanium Studioからプロジェクトをcleanして、改めてビルドしてみてください。

    もちろん、毎回毎回こんなことをやるのは面倒だし作業は手順が多ければそれだけ間違いが発生する可能性も増すので、普通は

    $ echo 'alias cleansim="rm -rf $HOME/Library/Application\ Support/iPhone\ Simulator/6.0/Applications/*"' >> $HOME/.bashrc
    $ source $HOME/.bashrc
    

    のようにして、cleansimと入力するだけで削除が実行できるようにしておくのが正しいやり方です。もちろん、バージョン依存が嫌いで

    #!/usr/bin/env ruby
    
    dir = "#{ENV['HOME']}/Library/Application Support/iPhone Simulator"
    version = Dir.entries(dir).sort.last unless ARGV[1]
    dir = "#{dir}/#{version}/Applications"
    Dir.entries(dir).each do |d|
      if !File.directory?(d)
        # .や..じゃない
        dir_to_remove = "#{dir}/#{d}".gsub(/ /, '\\\ ')
        system "rm -rf #{dir_to_remove}"
      end
    end
    

    を/usr/local/bin/clearsimとして保存している人もいます(これを書いてます)。

    TiCordova

    まあ、そんなに他意はないんですが、そういうのを作ってみました。

    このモジュールはWebViewに登録したイベントリスナーをリモートのHTMLから起動することが出来るようにするものです。使い方を間違えると大変なことになってしまいますが、例えば信用できる接続先で動作させるととても便利なものになります。

    var ticordova = require('org.selfkleptomaniac.ti.mod.ticordova');
    var webview = ticordova.createWebView({
      url:'http://example.com/test',
      top:0, height:Ti.UI.FILL,
      width:Ti.UI.FILL
    });
    
    webview.addEventListener('my_event', function(e){
      Ti.API.info(e);
      alert(e);
    });
    

    アプリ側はこのようにイベントリスナーを用意します。接続先のHTMLは

    <a href="fireecent://my_event/data/to/pass">Click</a>
    

    上のようにfireeventというschemaで別ページを開こうとしてください。するとmy_eventというイベントが発火してくれます。ちょっとしたPhoneGap/Apache Cordovaっぽいことができるようになると思うので、TiCordovaと名付けましたが、その辺は冗談です。

    Titanium MobileでLocal Notificationを実装する

    iOSにはリモートからメッセージなどを通知するPush Notificationとインストール済みアプリから通知するLocal Notificationという二つの機構があります。以前、Push Notificationのサンプルはgistに公開したことがあるので、今回はLocal Notificationについて書いてみます。

    Local Notificationが出来るのは

    ・ポップアップまたはステータスバーへの通知
    ・通知の際のサウンドの再生
    ・バッヂの操作(アプリのアイコンの数字のことね)

    です。通知が発生した際にステータスバーまたはポップアップのOKをタッチしてアプリケーションを起動することができます。端末がスリープ状態のときはロック解除のところが下のalertActionで指定する文字になります。通知がユーザに無視された、またはユーザがキャンセルボタンをクリックした場合は何も起きません。また設定で通知をオフにした場合は何も表示されません。

    *アプリを企画する方はここに注意してください。アプリを通知から必ず起動する方法はありません(やったらリジェクトされます)。

    それから、通知を予約したアプリがフォアグランドで実行中には通知は実行されません。ただし、通知が発生していたことはアプリに通知されます。後ほど詳しくみていきます。

    サウンドの再生ですが、どうやらシステムに組み込まれたサウンドを鳴らす方法はありません。サウンドデータを自前で用意することになります。対応しているフォーマットはcaf、aiff、mp3まで動作確認できました。wavとかもいけるかもしれませんが試していません。

    サウンドの再生は、仕様では30秒までとなっていますが、29秒でないと動作しませんでした。またボリュームはプログラム側から制御することができません。端末でミュートしていたら音は出ません(じゃなきゃ困ります)。

    繰り返しは毎日(daily)、毎週(weekly)、毎月(monthly)、毎年(yearly)から選択できます。何も指定しなければ一回だけ実行されます。繰り返しの場合は開始日時に過去の日付を指定すると次回からの実行になりますが、繰り返しがない場合は即時実行になってしまいますのでプログラム作成時に注意しましょう。

    以上を踏まえて、作りたいアプリがLocal Notificationを使うのに相応しいかどうかを判断しましょう。

    さて、実装です。Local Notificationではアプリいつ、どんなことをするかをiOS側のAPIに予約して、OKボタンの押下などの結果を受け取ります。Resources/app.jsにこんな内容を追記しましょう。

    Ti.App.iOS.cancelAllLocalNotifications();
    var notifications = [];
    notification_params = {
          alertBody: 'こんにちは、こんにちは',
          alertAction: 'OK',
          userInfo: {
            data: {param1:'これはparam1', param2:'これはparam2'}
          },
          sound: 'sound.mp3',
          repeat: 'daily',
          date: new Date((new Date()).getTime() +(1000 * 10))
        };
    notifications.push(Ti.App.iOS.scheduleLocalNotification(notification_params));
    

    まずcancelAllLocalNotificationsを実行して過去にこのアプリから登録したLocal Notificationを全てキャンセルにします。必要のないアプリであればここで実行する必要はありませんが、個別に通知を選んでキャンセルすることができないので、こうして一気にやってしまいます。

    notificationsという配列を使っていますが、後で利用することはありません。が、アプリの終了後にすぐに消えてしまうと困るのでこうしていちいち宣言しています。

    これでdateパラメータで指定された時刻(この場合は10秒後)に通知が実行されます。急いでアプリを閉じて待ちましょう。

    実行されましたか?

    あ、いまはまだこの記事を読んでいるだけで、実際に手を動かしてはいないんですね。わかりました。でも折角だからやってみましょう。今日は雨だし付き合いますよ。

    実行されましたか?

    よかったですね。ではもっと細かいところを見てみましょう。Local Notificationの予約はボタンを押せば実行されるようにしますか?でも、ボタンを押した瞬間にアプリを強制的に終了した場合はどうなるんでしょうか。変なタイミングで電話がかかってきてしまったら?Ti.App.iOS.scheduleLocalNotificationはvoid型で戻り値がないので、なんだか落ち着かない感じがしませんか?そうなんです、通常はLocal Notificationの予約はバックグランドで実行するのがベストプラクティスらしいので、ちょっと変更しましょう。値の受け渡しのデモもやります。

    app.jsはこんな感じ:

    Ti.App.Properties.setString('message', 'こんにちは、こんにちは!');
    Ti.App.Properties.setString('when','2012/04/23 00:15:00');
    var service = Ti.App.iOS.registerBackgroundService({url:'service.js'});
    

    service.jsをバックグランドで実行するようになっています。Resources以下に設置するservice.jsはこちら:

    if(Ti.App.Properties.hasProperty('when')){
    	Ti.App.iOS.cancelAllLocalNotifications();
    	var notifications = [];
    	notification_params = {
    	      alertBody: Ti.App.Properties.getString('message'),
    	      alertAction: 'OK',
    	      userInfo: {
    	        alertMessage:'ピンポンパンポン!'
    	      },
    	      sound: 'sound.mp3',
    	      repeat: 'daily',
    	      date: new Date(Ti.App.Properties.getString('when'))
    	    };
    	notifications.push(Ti.App.iOS.scheduleLocalNotification(notification_params));
    }
    Ti.App.currentService.stop();
    

    アプリがフォアグランドにある場合はLocal Notificationは実行されないといいましたが、実行するはずの時間になるとその旨だけがアプリに通知されます。これはnotificationというイベントとして捕まえることができます。userInfoを使って値の受け渡しまで実行できます。

    Ti.App.iOS.addEventListener('notification', function(e) {
      var dialog = Ti.UI.createAlertDialog({
      	title:'お知らせ',
      	message:e.userInfo.alertMessage,
      	buttonNames:['OK', 'お黙り'],
      	cancel:1
      	});
      dialog.show();
    });
    

    こんなもんでしょうかね。Local Notificationのパラメータにはいくつか面白いものもあるのでぜひマニュアルをご覧ください。

    Titanium MobileではAnalyticsを切っておきましょう

    2年前の記事ですが、AppStoreのトップ無料や人気上位のアプリの68%がUDID(Universal Device Identifier)を送信しているという報告がありました(関連の日本語記事)。

    利用状況などを収集してアプリの改善に利用するのはよくあることだとは思います。行動分析のためにデータを収集する場合、端末の識別のために何らかのIDが必要となるわけですが、スマートフォンでは最も簡単に利用できるのがこのUDIDです。このようなIDは以前から日本の携帯電話のサービスプロバイダでは広く利用されており、中にはウェブサービスの認証にまで使われているケースもありましたので、ある意味われわれにとっては非常に身近なものでもあります。

    しかし、端末固有の一意なIDとして何かと便利に思われるUDIDですが、これを第三者に送信するのはあまり感心できません。上の報告ではHTTP通信のGETリクエストで送信しているアプリもあったようですが、これは論外です。しかし、もちろんそれだけが理由ではありません。

    UDIDの送信がなぜ問題なのかについては諸説あるでしょうが、主な理由として考えられるのがエンドユーザの行動をトラッキングできてしまうことがプライバシーの侵害になるかもしれないという懸念です。同じようなことがいわれてきたブラウザのクッキーであれば、第三者のクッキー(Third-party Cookie)はブラウザの設定などでオプトアウトできるわけですが、UDIDは変更することができないため一度送信されてしまえば相手側の対応がなければ削除されません。アプリによってはその性格上、いつ起動していつ利用されたのか他人に知られたくないケースもあるでしょう。しかしUDIDが取得されてしまえば、第三者にその情報が漏れてしまいます。この手の試みはこれまでもウェブ広告の世界などでプライバシーとビジネスの間のせめぎ合いが何度も繰り返されてきた歴史があるわけですが、たいていはプライバシーの擁護が重視される結論に至っています。

    ところでTitanium Mobileですが、Appcelerator社のサービスに登録している方なら一度くらいはAnalyticsの画面を見たことがあるかと思います。アプリのインストールされた数の推移や様々な行動分析が見られるので、開発者だけでなく企画等の担当者も大変重宝します。これらの情報はTitanium MobileのAnalyticsという機能を利用して送信されています。送信はSSLで暗号化されたPOSTリクエストになっているので、途中で覗き見られることについては、iPhoneの3G回線の通信がEnd-to-endのSSLではないなんてことはないでしょうから(ガラケーだとゲートウェイとウェブサーバ間のSSLになっていたりするようですが)、さほど憂慮する必要はなさそうです。さらに、当然ながらAppcelerator社は集積したデータをさらに第三者に転売したり、広告などに悪用したりするようなことはしないと明言しています。その点で現時点で懸念する必要はないと思います。しかし、いくつか問題はあります。(1)データを集積しているAppcelerator社はアプリの配信元、エンドユーザにとっては無関係の第三者であること(2)送信される情報についてアプリの利用者から明確な許諾を得ていないこと(3)UDIDが送信されていること、などがそうです。

    これらの対応策は非常に簡単で、tieapp.xmlをテキストエディタなどで開いて

    <analytics>false</analytics>
    

    とするだけでこの機能をオフにすることができます。個人的な意見ですが、上記の理由から、現時点ではリリースされるアプリについては必ずこれをやっておく方がよいでしょう。もちろん、UDIDの送信が即、何か悪いことをしている証拠にはならないと思います。しかし、このような取り扱いに注意が必要なデータについては、トラブルを避けるためにも万全な状況でなければ利用しない方がいいはずです。また、iOS5からUDIDの取得が非推奨となったことに伴い、やはりUDIDの送信は避けるべきでしょう。

    実際のところどうなっているのか確認してみましたが、残念なことに1.8系の実装(iphone/Classes/PlatformModule.mやiphone/Classes/TiUtils.mを参照のこと)を見る限り、Titanium.Platform.idを送信していますね。1.8.0.1の場合はiphone/Classes/AnalyticsModule.mmを見るとわかります。実際にデータをダンプするには(SSLの通信をキャプチャするような面倒なことが好きな人を除いて)このファイルを下のように変更します。

    $ git diff iphone/Classes/AnalyticsModule.mm 
    diff --git a/iphone/Classes/AnalyticsModule.mm b/iphone/Classes/AnalyticsModule.mm
    index f76a472..46c6e81 100644
    --- a/iphone/Classes/AnalyticsModule.mm
    +++ b/iphone/Classes/AnalyticsModule.mm
    @@ -217,6 +217,9 @@ NSString * const TI_DB_VERSION = @"1";
            [request setUseCookiePersistence:YES];
            [request setShouldRedirect:YES];
            NSString * stringifiedData = [SBJSON stringify:data];
    +  //debug
    +  NSLog(@"[DEBUG] Analytics sending data: %@", stringifiedData);
    +  //debug
            [request appendPostData:[stringifiedData dataUsingEncoding:NSUTF8StringEncoding]];
            [request setDelegate:self];
    

    ご覧頂ければわかる通り、UDIDが送信されていますので、やはり上に書いた方法でAnalyticsは停止した方がいいですね。それと、Appcelerator社が悪意をもってこの機能を実装しているのではないこともおわかり頂けるかと思います。だって、そうじゃなきゃソース公開してないですよね?個人的にはこういうところがTitanium Mobileを使い続けることの大きな理由になっています。

    *決してここの部分をいたずらして集積サーバに変なデータを送り込んでしまおうとか考えてるわけじゃないですからね!

    また、この点について各方面に問い合わせたところ、Analytics自体は非常に有望なサービスなので、今後はUDIDを利用しないよう変更する予定になっているとのことでした。そのときが来たら再度検討してみたいと思います。

    この数日、エントリを書いておきながら公開するべきか非常に悩んでいました。上で説明はしたつもりですが、これは別に情報が勝手に第三者に集積されてしまったというような事件ではなく、単純に機能としてソースまで公開され、ドキュメントにも書かれていることなわけで、簡単に調査も可能なことですから、開発者としては知っていてある意味当然なことです。ただ、どこかで誰かがこの機能について悪意的にかき立てるようなことがあれば、まだまだ少数のコミュニティで活発にやり取りしている状態のTitanium Mobileが今後プラットフォームとして成長する妨げになってしまうような打撃を受けると嫌だな、と思ったので、情報の周知徹底を目的に公開することにしました。

    もう一度まとめます。

    (1)Analyticsはオフにできるよ
    (2)オフにしとけばなんにも怖くないよ
    (3)UDIDは今後は使われなくなるよ

    以上です。

    Titanium Mobileでアプリ内課金(iOS編)

    アプリ内課金の実装方法について知りたいという声があったので、参考になればと書いておきます。アプリ開発者の方は権利の15%で取引に応じますの苦労がこれで少しでも減るなら喜ばしいことです。とはいえ、世知辛い世の中ですから、このコードで生じた金銭的問題の責任は放棄しますので、ご利用の際はあくまでも参考程度にとどめ、十分検証した上で実装されるようお願いします。

    一応、動作はTiStorekit1.4、Titanium Mobile SDK 1.8.1、非消費型コンテンツで確認しています。

    iOSの場合、課金の手続きは

    • In App Purchaseが有効になっているかを確認する
    • 正しいProduct IDかを確認する
    • 購入開始
    • 成功ならレシートを受信してチェック、キャンセルやエラーならエラー処理

    となります。ここではAppcelerator社から公開されているTiStorekitを使った実装の例で説明します。以前に実装したものを参考にざっと書いた関係で、細かい条件は検証していませんのであしからず。CoffeeScriptだけどいいよね?

    事前にiTunes ConnectでIn App Purchaseの設定が完了していることが前提になります。iTunes Connectの使い方は割愛しますが、支払い関連の設定が完了していないとIn App Purchaseは有効になりません。またProvisioning Portalで設定を確認して、正しいProvisioning Profileでビルドしましょう。iOS5からシミュレータからも利用できるようになったようですが、必ず実機で動作確認してください。

    そうそう、下のコードではコンストラクタで@SANDBOXをtrueにしていますが、リリース時には変更しましょう!

    
    class InAppPurchase
      constructor:()->
        @ERROR_PRODUCT_ID = {type:'product_id', message:L('inapp_error_product_id')}
        @ERROR_CANMAKEPAYMENT = {type:'canmakepayment', message:L('inapp_error_settings')}
        @ERROR_UNKNOWN = {type:'unknown', message:L('inapp_error_unknown')}
        @SANDBOX = true
    
      start:(data, success, error)->
        ###
        args  dataは課金対象の商品オブジェクト({product_id:プロダクトID})
              sccess、errorはそれぞれcallback関数
        ###
    
        storekit = require 'ti.storekit'
        if data.product_id == '' || data.product_id == null || data.product_id == undefined
          # なんて防衛的なコード! # 
          error(@ERROR_PRODUCT_ID)
        else if !storekit.canMakePayments
          # AppStoreが有効になっていない #
          error(@ERROR_CANMAKEPAYMENT)
        else
          # 正しいProduct IDかどうかチェック #
          storekit.requestProducts([data.product_id], (e)=>
            if e.success
              product = e.products[0]
              if product != null
                _success = success
                _error = error
                storekit.purchase(product, (elem)->
                  if elem.state == storekit.FAILED
                    error(@ERROR_PRODUCT_ID)
                  else if elem.state == storekit.PURCHASED
                    callback = (response)=>
                      if response.success && response.valid
                        _success()
                      else
                        _error(@ERROR_UNKNOWN)
    
                    # subscriptionの場合は下のオブジェクトにsharedSecretプロパティを追加 #
                    args = {
                      receipt:elem.receipt
                      sandbox:@SANDBOX
                      callback:callback
                    }
                    storekit.verifyReceipt(args)
                  else if elem.state == storekit.RESTORED
                    storekit.restoreCompletedTransactions()
                    success()
                  else
                    Ti.API.info "purchasing..."
                )
              else
                error(@ERROR_UNKNOWN)
            else
              error(@ERROR_PRODUCT_ID)
          )
    TiStorekit = new InAppPurchase()
    TiStorekit.start(product, success, error)
    
    

    canMakePaymentsは定数でiOSの設定画面でAppStoreが有効になっていないのでそもそもアプリ内で課金ができない場合はfalseになります。requestProductsでProduct IDが正しいかどうかを確認し、purchaseで購入処理を開始します。successが返ればveryfyReceiptで確認して完了処理を実行します。

    レシートの確認まで実装しましたが、ここは迷うところですね。通信に時間がかかったり、またはこのタイミングで処理が中断してしまった場合、利用者にとってはお金を払ったのに購入が正常に完了しないように見えてしまいます。時間がかかるようならキューに入れてレシートは後で確認し、いったん完了処理をしてから、不正があったらそれなりの処理をする、といったような手段で対応するしかないでしょう。かといってレシートを確認しないと、不正な利用を完全に防ぐことができなくなります。