iPhone で change イベントが発火されすぎる問題

2014年12月26日 20:29 3689

ツイート中で「ドラム」と書いているインターフェースの正式名称が知りたくて調べてみたところ、公式の iOS Developer Library の UIPickerView Class Reference にこんな記述を見つけた。

The UIPickerView class implements objects, called picker views, that use a spinning-wheel or slot-machine metaphor to show one or more sets of values.

どうやら HTML フォームの select みたいに1行に1つの要素が表示されるものをスピニングホイール(spinning-wheel)、日付のピッカーのように列が分かれているものをスロットマシーン(slot-machine)と表現している。なかなか的を射たメタファーだと思う。

さて本題というか、select タグで作られたセレクトメニューの場合、選択要素の変更をイベントトリガーにするにはその select 要素の change イベントを利用する。たとえば jQuery ならこんな感じだ(関数 onChange() は定義済みとする)。

$('.selectMenu').change(onChange);

問題はイベントが発火されるタイミングである。iOS の Mobile Safari のスピニングホイールだと、ホイールを1ステップ回すたびにイベントが発火されるのだ。よって、上記の場合は onChange() がその都度呼ばれて都合が悪い。大方の心情としてはおそらく、目的の値まで回し終えて「完了」ボタンをタップした時点で発火してほしいはず。

ではどうするか。同じ iOS でも iPad はスピニングホイールが表示されないので、UA 判定で iPhone の場合のみ処理を変えるしかない。幸い、スピニングホイールを閉じるという動作に対して focusout イベントが発火されるそうなので、これを利用する。

var event;
$('.selectMenu').change(function(e) {
    if (navigator.userAgent.indexOf('iPhone') != -1) {
        event = e;
    } else {
        onChange(e);
    }
});
if (navigator.userAgent.indexOf('iPhone') != -1) {
    $('.selectMenu').focusout(function() {
        if (event) {
            onChange(event);
        }
    });
}

もうちょっとスマートな書き方があるような気もするが、とりあえずこれで。