iPhoneSDK開発のレシピのレシピ47「UIWebView をフィンガージェスチャーで操作する」書かせていただいたのですが、この処理でプライベートAPI を使用しているために、以下のように修正しまさせていただきました。GitHub のサンプルコードでは既に先月修正済みなのですが正式にアナウンスしていなかったので、改めて説明させていただきます。

やりたいことは、Firefox などのマウスジェスチャーのように UIWebView をフィンガージェスチャーで操作するということです。UIWebView ではシングルタッチはスクロールや拡大縮小などがあるため、2本指でのタッチで左右にスワイプしたときに戻る、進むという動作をさせることにします。(フレーム内のスクロールに2本指でのタッチを使用しますが、まあその辺はとりあえず置いておいて下さい)

詳細なコードは GitHub にありますので、詳しくはそちらをご参照ください。

ポイントはフィンガージェスチャーを認識するためにタッチ動作をフックする UIWindow のサブクラス GestureWindow を作り、AppDelegate でその GestureWindow を使用するところです。UIWindow の sendEvent: ですべてのタッチ動作をフックして UIWebView への2本指でのマルチタッチのときのみ、delegateを通じてタッチイベントを通知します。また、フックしたタッチイベントは全てスーパークラスへそのまま渡すことにより、通常のタッチイベントを邪魔しないようにします。

@protocol GestureWindowDelegate

- (void) touchesBeganWeb:(NSSet *)touches withEvent:(UIEvent *)event;
- (void) touchesMovedWeb:(NSSet *)touches withEvent:(UIEvent *)event;
- (void) touchesEndedWeb:(NSSet *)touches withEvent:(UIEvent *)event;

@end

@interface GestureWindow : UIWindow {
    UIWebView* wView;
    id delegate;
}

@property (nonatomic, retain) UIWebView* wView;
@property (nonatomic, assign) id delegate;

@end
@implementation GestureWindow

@synthesize wView, delegate;

-(void) dealloc {
    [wView release];
    [super dealloc];
}

- (void)sendEvent:(UIEvent *)event {
    [super sendEvent:event];
    if (wView == nil || delegate == nil) {
        return;
    }
    // 2本指でのマルチタッチか
    NSSet *touches = [event allTouches];
    if (touches.count != 2) {
        return;
    }

    UITouch *touch = touches.anyObject;
    // 指定のUIWebViewへのタッチか
    if ([touch.view isDescendantOfView:wView] == NO) {
        return;
    }

    switch (touch.phase) {
        case UITouchPhaseBegan:
            if ([self.delegate
                 respondsToSelector:@selector(touchesBeganWeb:withEvent:)]) {
                [self.delegate
                    performSelector:@selector(touchesBeganWeb:withEvent:)
                    withObject:touches withObject:event];
            }
            break;
        case UITouchPhaseMoved:
            if ([self.delegate
                 respondsToSelector:@selector(touchesMovedWeb:withEvent:)]) {
                [self.delegate
                    performSelector:@selector(touchesMovedWeb:withEvent:)
                    withObject:touches withObject:event];
            }
            break;
        case UITouchPhaseEnded:
            if ([self.delegate
                 respondsToSelector:@selector(touchesEndedWeb:withEvent:)]) {
                [self.delegate
                    performSelector:@selector(touchesEndedWeb:withEvent:)
                    withObject:touches withObject:event];
            }
        default:
            return;
            break;
    }
}

@end

関連する投稿