UIButtonとUITapGestureRecognizerの相互作用がiOS5以前と6以降とで違うという話

モチベイション

たとえば、画面上にテキストフィールドを配置して入力を促すことを考える。このとき、画面のテキストフィールド外をタップした場合はフォーカスを外してキーボードを隠すなどの処理を行いたいことがある。こういった場合、画面全体のviewに対してUITapGestureRecognizerを追加してタップイベントを拾うのが常套手段だろう。

ところでここにボタンが登場する。仮に送信ボタンとする。ボタンはボタンで同時にタップイベントを処理しており、タップされれば反応する。このとき先に追加したUITapGestureRecognizerとの絡みで問題が起こりはしないかと心配になるけれども、特に問題はない。送信ボタンは何事もなく反応し送信する。

……iOS6以降ならば。

事象と原因

iOS5で同じことをすると、ボタンのタップアクションは無視される(ハイライトは起こる)。Stack Overflowによれば、

On iOS 5, a UITapGestureRecognizer on a button's superview interferes with the action of the button. On iOS 6, they fixed this: they introduced a UIView event gestureRecognizerShouldBegin:, and a button automatically returns NO for a tap gesture recognizer attached to a superview.

ということらしい。ちなみに、この状態でも長押ししてから離すとタップアクションが起こる(これはどうやらUIResponderのtouchesEnded:withEvent:を経由して起こっているようだが詳細は不明)。

処方箋

UIGestureRecognizerのデリゲートメソッドでタップイベントを選択的に無視することができる。こんな具合である:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    // ボタンを避ける(iOS6以降では勝手に避けてくれるけれど)
    if (touch.view == _submitButton) {
        return NO;
    }
    return YES;
}

UITapGestureRecognizerのdelegateselfに設定することを忘れないように注意が必要である。

参考