アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

Titanium の WebView で HTML をホスト

Titanium の WebView で HTML をホストする方法の覚え書き。

WebView

WebView は Titanium.UI.WebView として定義される Webブラウザ コントロールである。URLHTML ソースを指定することでページが表示される。例えば以下のように使用する。

var window = Titanium.UI.createWindow();

// URL
var webView = Titanium.UI.createWebView( {
  url: "http://www.example.com/"
});

// HTML を直に指定
var webView2 = Titanium.UI.createWebView( {
  html:    "<!DOCTYPE html><html>... 中略 ...</html>"
});

window.add( webView  );
window.add( webView2 );
window.open();

アプリと WebView 内ページの連携

WebView は単にページを表示するだけでなく、アプリ側と連携をおこなう仕組みも提供している。アプリ側からの通知を受け取る場合、WebView 管理下の JavaScript で以下のようなハンドラ関数を定義。

/**
 * Titanium アプリからの通知によって発生します。
 *
 * @param {Object} params Titanium アプリから指定されたパラメータ。
 */
function onTitanium( params ) {
  // params を利用した処理 ...
}

アプリ側の WebView インスタンスが webView なら以下のように呼び出す。

var js = 'onTitanium( { text: "テキスト" } )';
webView.evalJS( js );

WebView.evalJS メソッドは eval という名前のとおり指定された JavaScript を評価する。よってページ内に読み込まれた関数の呼び出しを記述すれば、それが実行される。

次は WebView 内のページ側からアプリへの通知。まず、アプリ側にイベント リスナーを追加する。

/**
 * WebView 内ページからの通知によって発生します。
 *
 * @param {Object} params Titanium アプリから指定されたパラメータ。
 */
Titanium.App.addEventListener( "onWebView", function( params ) {
  // params を利用した処理 ...
} );

ページ側は、これを以下のように呼び出す。

// Ti.App.fireEvent でも可
Titanium.App.fireEvent( "onWebView", { text: "テキスト" } );

WebView 内に読み込まれたページには Titanium.App.fireEvent という特殊なメソッドが定義されている。これがアプリ側への橋渡しを担う。アプリ→ページのときと異なりパラメータには JavaScript オブジェクトをそのまま指定できる。

これらの仕組みを利用することで Titanium にない機能を Web サービスで補ったり、サービスから端末固有の機能にアクセスしたりできる。ネイティブアプリのブラウザ コントロール (Android = WebView、iOS = UIWebView) でも同様の仕組みを用意しているけれど Titanium の特性上、これらがクロスプラットフォーム化されることになる。

リソース HTML の表示

WebViewurl プロパティには Titanium プロジェクト内のリソース HTML を指定できる。

パスのルートは Resources ディレクトリとなり、URL の指定は相対パスでよい。ただし Titanium の JavaScript とリソース HTML から読むものを区別する意味で専用のサブ ディレクトリを設けたほうがよさそうだ。

例えば Resouces の下に web という名前のフォルダを用意するとして、その中の index.html を表示する場合の処理は、以下のようになる。

var window  = Titanium.UI.createWindow();
var webView = Titanium.UI.createWebView( { url: "web/index.html" });

window.add( webView  );
window.open();

リソース HTML を利用するならオフラインでもよいので、アプリの GUI を Web ベースで実装したいときや、ヘルプのような資料を表示する場合に役立つ。

サンプル アプリ

前にこのブログで書いた Titanium と Aptana でモバイル開発ではマップを操作するサンプルを作成したが、今回は Web サービスの Google Maps API V3 を WebView でホストして同等の機能を実現してみた。マップ読み込みはリソース HTML + JavaScript でおこなっている。

サンプル アプリのスクリーンショット

Titanium のプロジェクトは build ディレクトリも含めないと不完全なのだが、サイズが大きすぎるのでやめておく。代わりに Resources ディレクトリ内を ZIP 圧縮したものを公開する。

動作を確認する場合は Titanium 上で適当なプロジェクトを作成して Resouces ディレクトリをサンプルのものに置き換える。ただしサンプルの Resources には iphone 向けのデータを含んでいないため、Mac OS X + iOS SDK な環境で試すなら、元の Resouces に入っている iphone というディレクトリの中身を流用する。

WebView のバグ

Titanium の WebView でリソース HTML を利用してみたとろ、バグがあることに気づいたので記録しておく。

バグ 1 : ページ全面を覆う div で Google Map を表示できない

問題が発生すると以下の画像のような状態となる。

マップが表示されないバグのスクリーンショット

サンプル アプリを動かすと WebView 部分に縦スクロールバーが表示されているが、それはこの問題を回避するための CSS 指定によるものである。サンプルアプリの CSS は以下のようになっている。

@charset "utf-8";

html, body {
  height: 100%;
  margin: 0px;
}

#map_canvas {
  height: 100%;
  margin: 0px 1px 1px 0px;
}

縦横 1 ピクセルの余白を入れ全面表示されないようにした。CSS で overflow:hidden を指定すればスクロールバーを消せるけど、そうすると問題が再現する。JavaScript で動的に指定してもダメだった。HTML や CSS をいろいろ変更してみたが全面表示かつスクロールバーなしは実現できてない。識者のかた意見を求む。

この問題は Titanium の WebView & Android 限定らしく、ネイティブな Android プロジェクトの assets に件のリソースを追加して WebView へ指定したが、再現しなかった。また Titanium でも iPhone の場合は発生しなかった。

バグ 2 : iPhone でリソース HTML を利用した場合、history が記録されない

この問題は jQuery Mobile を試していて気がついた。jQuery Mobile では以下のような定義を特別なページとして扱い、それが遷移先だった場合はヘッダ部分へ自動的に Back ボタンをつけてくれる。

<div data-role="page">
  <div data-role="header">
    <h1>タイトル</h1>
  </div>
  <div data-role="content">
    ページの内容
  </div>
</div>

通常は Back ボタンを押したときに元のページへ戻るのだが、以下の条件が揃うと動作しない。

  • Titanium Mobile の WebView で表示している
  • ページは Web 上ではなくリソース HTML
  • iPhone 上で動作している

はじめはタップがうまく処理できていないのか Back ボタン絡みでスクリプト エラーになっているかと予想していた。しかし Firebug で動作確認してみたところ、このボタンが押されたときは以下のコード (jQuery Mobile 1.0 Alpha 2 のものとなる) が実行される。

$( "<a href='#' class='ui-btn-left' data-icon='arrow-l'>"+ o.backBtnText +"</a>" )
  .click(function() {
    history.back();
    return false;
  } )

history はページ遷移を記録するオブジェクトで Web ブラウザーの Back/Forward 操作を JavaScript からおこなう時に利用する。history.back() は Back 操作に相当する。もちろん iPhone Safari もサポートしているし Titanium の WebView に指定する先がオンラインならバッチリ動作する。

ではなぜリソース HTML だと動かないのか。history オブジェクト履歴数を length プロパティに持っているので表示してみたところ、リソース HTML の場合はいくらページ遷移しても 1 のまま増加しない。どうやら履歴を記録できていないようだ。

対策を考えてみたが、うまい方法が思い浮かばない。WebView 連携を知ったとき jQuery Mobile を GUI に使えるのでは?と思ったのだが、この問題により断念している。