読者です 読者をやめる 読者になる 読者になる

imthinker::net

ふらふらうぇぶろぐ

Titanium で TableViewRow 長押しイベント

最近の TableView を使った iOS アプリは Row を長押しすると何らかのアクションを実行できるものが多いです。 Sparrow は未読 / 既読 のフラグ切り替えができますし、 NewsStorm はアクションを選択するためのメニューが表示されます。

しかし、 Titanium Mobile でこれを実現しようと思っても longpress や longtap イベントは用意されていません。 touchstart / touchend / touchcancel を組み合わせると何とかできそうですが…。今回は長押しイベントを再現してみましょう。

下ごしらえ

まずは TableView が乗っかったひな形を用意します。ファイル名は app.js で。

/*jslint devel: true */
/*global Titanium */
(function (Ti) {
    "use strict";
    var win, table, row, i, l, data;
    win = Ti.UI.createWindow({
        backgroundColor: "#FFFFFF"
    });
    table = Ti.UI.createTableView({
        data: []
    });
    for (i = 0, l = 10, data = []; i < l; i += 1) {
        row = Ti.UI.createTableViewRow({
            title: "TableView #" + i
        });
        data.push(row);
    }
    table.data = data;
    win.add(table);
    win.open();
}(Titanium));

これで10個の Row を持つ TableView が載ったアプリができあがりました。

イベント処理

次に、 TableViewRow に長押しイベントをくっつけていきます。 JavaScript は {} がブロックスコープを提供しないので、 for や while の中で作った要素にイベントを持たせるときは注意が必要です。 app.js を改造してみましょう。

/*jslint devel: true */
/*global Titanium */
(function (Ti) {
    "use strict";
    var win, table, row, i, l, data, press;
    win = Ti.UI.createWindow({
        backgroundColor: "#FFFFFF"
    });
    table = Ti.UI.createTableView({
        data: []
    });
    function longPress(row) {
        return function () {
            press = true;
            setTimeout(function () {
                if (press) {
                    row.backgroundColor = "#FFFFFF";
                    console.log(row.title);
                    press = false;
                }
            }, 500);
        };
    }
    function cancelPress() {
        press = false;
    }
    for (i = 0, l = 10, data = []; i < l; i += 1) {
        row = Ti.UI.createTableViewRow({
            title: "TableView #" + i,
            backgroundColor: "#FFFFFF"
        });
        row.addEventListener("touchstart", longPress(row));
        row.addEventListener("touchmove", cancelPress);
        row.addEventListener("touchend", cancelPress);
        data.push(row);
    }
    table.data = data;
    win.add(table);
    win.open();
}(Titanium));

こんな感じになりました。 row には touchstart / touchmove / touchend イベントを設定しています。 touchmove / touchend は同じ関数 (cancelPress) が呼び出されます。

問題は touchstart イベントです。定義時に row を引数に longPress 関数を実行して、関数を返却してもらっています。返却してもらった関数だけ抜き出すと、

function () {
    press = true;
    setTimeout(function () {
        if (press) {
            row.backgroundColor = "#FFFFFF";
            console.log(row.title);
            press = false;
        }
    }, 500);
}

こうです。中では setTimeout を使って 500ms 遅延させて関数を実行させています。このとき、 press の値が true だったら row の長押しにヒモづけたい処理が走るわけです。指が動いたり、タップを止めれば cancelPress 関数によって press の値は false になります。

この例では 500ms の間 row を長押ししていれば console.log によって受け取っていた row.title がコンソール上に記録されるはずです。

row.backgroundColor の謎

さて、 if (press) の直下に row.backgroundColor = "#FFFFFF"; があります。 Titanium Mobile 自体のバグなのか、仕様なのか分かりませんが、 row の背景色を変化させると touchstart イベントをキャンセルできます

もしも背景色を変化させないと、いつまでたっても row を押しっぱなしになってしまいます。正直、黒魔術だと思いますが、これで長押しイベントを再現することができました。

Titanium 2.1.x からこの状態は続いていて、 3.0 でどうなるのか様子を見ていましたが、 3.0.0.GA でも Continuous Builds (3.1.x) でも同じように動くことから、しばらくは大丈夫そうだなということで公開します :-)

CODESTRONG!