iOSでボタン連打を防ぐ

よくあるパターンで、ボタンが押されるとナビゲーションコントローラーを使って次の画面に遷移させるってのがありますよね。
例えばこんな感じ。

- (IBAction)onPushButton:(id)sender
    HogeViewController *viewController = [[[HogeViewController alloc] init] autorelease]; //まあ厳密にはこれじゃだめだけど
    [self.navigationController pushViewController:viewController animated:YES];
}


ここで問題がありまして、ボタンを連打しまくると、上の例でいえばHogeViewControllerのインスタンスが複数作られてプッシュされてしまう事があるんですね。
これを回避するためには、

- (IBAction)onPushButton:(id)sender
    if (!self.isButtonPushed){ 
        self.isButtonPushed = YES;
        HogeViewController *viewController = [[[HogeViewController alloc] init] autorelease]; //まあ厳密にはこれじゃだめだけど
        [self.navigationController pushViewController:viewController animated:YES];
    }
}

みたいな感じに、何かしらの変数を使ってボタンが1回押された後は、また押されても無視するように組み込めばいいわけです。
ただしこれだと、遷移先から戻ってきたときにまた判定変数をクリアしないといけないのが面倒なのと、そもそも戻るボタンから戻ってきた時にそれを検知するのがめんどくさいというのがあります。
あと全てのボタン1つ1つにこれを実装するのは大変に労力がかかります。


そこでまあ、Windowsみたいにイベントをフックできないのかなーって思ってたら、どうもUIWindowのsendEventなるメソッドをオーバーライドすれば良いらしい。
UIWindowを継承して、カスタムWindowクラスを作ります。

@interface CustomWindow : UIWindow
{
    CFAbsoluteTime _lastTime;
}

@implementation CustomWindow

- (id)initWithFrame:(CGRect)frame
{
    _lastTime = 0;
    return [super initWithFrame:frame];
}

- (void)sendEvent:(UIEvent *)event
{
    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    if (touch.phase == UITouchPhaseBegan){
        CFAbsoluteTime t = CFAbsoluteTimeGetCurrent();
        if (t - _lastTime > 0.1){
            [super sendEvent:event];
        }
        _lastTime = t;
    }else{
        [super sendEvent:event];
    }
}

まあこんな感じに、100ms以内にタッチイベントが再び発生したら無視します。
スーパークラスのメソッドを呼ばなければ、子ビューには一切タッチイベントが送られないようです。


ただ、これだと今度はダブルタップとかを判定できなくなるので、必要ならダブルタップ専用のViewクラスを用意して、上のメソッドの中でそのクラスの派生クラスだったら無条件にスーパークラスを呼べばいいんじゃないでしょうか。