UIWebView の touch イベントをフックする方法

http://son-son.sakura.ne.jp/programming/iphone_sdk_hach_uiwebview_how.html

この方向でいろいろやってみたけど、やはりズームの挙動があやしくなってしまって、結局うまく動かなかった。

そこで、method swizzling でやってみたらうまくいったので、紹介しておきます。

http://github.com/psychs/iphone-samples/tree/master/WebViewTappingHack

@implementation UIView (__TapHook)

- (void)__touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
  [self __touchesEnded:touches withEvent:event];
  
  id webView = [[self superview] superview];
  if (touches.count > 1) {
    if ([webView respondsToSelector:@selector(fireZoomingEndedWithTouches:event:)]) {
      [webView fireZoomingEndedWithTouches:touches event:event];
    }
  }
  else {
    if ([webView respondsToSelector:@selector(fireTappedWithTouch:event:)]) {
      [webView fireTappedWithTouch:[touches anyObject] event:event];
    }
  }
}

@end

static BOOL hookInstalled = NO;

static void installHook()
{
  if (hookInstalled) return;
  
  hookInstalled = YES;
  
  Class klass = objc_getClass("UIWebDocumentView");
  Method targetMethod = class_getInstanceMethod(klass, @selector(touchesEnded:withEvent:));
  Method newMethod = class_getInstanceMethod(klass, @selector(__touchesEnded:withEvent:));
  method_exchangeImplementations(targetMethod, newMethod);
}

@implementation PSWebView

- (id)initWithCoder:(NSCoder*)coder
{
  if (self = [super initWithCoder:coder]) {
    installHook();
  }
  return self;
}

- (id)initWithFrame:(CGRect)frame
{
  if (self = [super initWithFrame:frame]) {
    installHook();
  }
  return self;
}

- (void)fireZoomingEndedWithTouches:(NSSet*)touches event:(UIEvent*)event
{
  if ([self.delegate
         respondsToSelector:@selector(webView:zoomingEndedWithTouches:event:)]) {
    [(NSObject*)self.delegate
       webView:self zoomingEndedWithTouches:touches event:event];
  }
}

- (void)fireTappedWithTouch:(UITouch*)touch event:(UIEvent*)event
{
  if ([self.delegate
         respondsToSelector:@selector(webView:tappedWithTouch:event:)]) {
    [(NSObject*)self.delegate
       webView:self tappedWithTouch:touch event:event];
  }
}

@end

追記 (2008/11/7)

実用時に必要なチェックを追加しました。

  • method swizzling が 1度しか行われないようにした
  • UIDocumentView の superview の superview が自前の WebView クラスであることを確認してからイベントを投げるようにした