UIWebViewのリンク長押しを簡単にハックする

UIWebViewを長押しすると下からニュっとアクションシートが出てくるわけですが、これの中身を変えたくなりました。 ネット上で探したところ・・・・これとか解決策っぽいのはあったんですが、なんかもうやること多いし、UIWindowのサブクラスを作らなきゃいけないし、英語だし、なんか嫌だったので、もっと簡単な方法でハックしてみました。 ちょっと精度が悪かったり、反応しない時があったりとポンコツ気味ですが、簡易的に作るならこれぐらいがいいなーって感じです。

やり方

大雑把な手順としては

  1. 本来のリンク長押しの機能をオフにする
  2. jsでリンク長押しを検知してobj-cの方に教えてあげる
  3. 普通にアクションシートを出してあげる

という3段階構成です。 「本来のリンク長押しの機能をオフにする」と「jsでリンク長押しを検知してobj-cの方に教えてあげる」はどっちもjsなので、 具体的にはとあるjsを読み込んで、通知が来たらアクションシート出すだけというお手軽構成です。

本来のリンク長押しの機能をオフにする

css(style)で–webkitTouchCalloutをnone指定にしてあげるとでなくなります。 こういうのはSafari Web Content Guideに書いてあるっぽいです。 で、これをjsでかくと

1
document.documentElement.style.webkitTouchCallout = 'none';

です。つまり

1
[webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitTouchCallout = 'none';"];

とやれば長押ししてもきかなくなります

jsでリンク長押しを検知してobj-cの方に教えてあげる

ここが少しめんどくさくて、jsのタイマー使ってリンクの長押しを検知しーとかやらないといけないです。 といっても、中身がわからなくてもいいから使えればいいんだ!っていうなら簡単で、次のjsをロードすればいいだけです。

1
var NVTimer = { currentUrl : null, currentTitle : null, start : function(url, title){ NVTimer.currentUrl = url; NVTimer.currentTitle = title; setTimeout("NVTimer.finish('" + url + "')",750); }, cancel : function(url){ NVTimer.currentUrl = null; NVTimer.currentTitle = null; }, finish : function(url){ if(NVTimer.currentUrl === url){ document.location = "nv://taphold"; } } }; (function(){ var elements = document.getElementsByTagName("a"); var length = elements.length; var i = 0; for(i = 0; i< length; i++){ if(elements[i].href !== undefined ){ elements[i].onselectstart = function(){ return false; }; elements[i].ontouchstart=function(){ NVTimer.start(this.toString(), this.innerHTML); return true; }; elements[i].ontouchcancel=function(){ NVTimer.cancel(this.toString()); return true; }; elements[i].ontouchend=function(){ NVTimer.cancel(this.toString()); return true; }; elements[i].ontouchmove=function(){ NVTimer.cancel(this.toString()); return true; }; } } })();

これをロードすれば、リンクを長押しした時に「nv://taphold」といったURLに移動しようとするので、これをobj-c側で検知してあげます。 ロードするときは、最初の「本来のリンク長押しの機能をオフにする」の方のjsと一つにまとめて、xxx.jsって形でファイルに保存して ページのロード完了時に読み込むのがおすすめです。 ※jsファイルをそのままプロジェクトに追加しただけだとリソースファイル扱いになってないので注意

1
- (void)webViewDidFinishLoad:(UIWebView *)webView{ NSString \*nvtimer = [_webView stringByEvaluatingJavaScriptFromString:@"typeof NVTimer"] ; if([nvtimer isEqualToString:@"undefined"]){ NSString \*touchhold = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touchhold" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]; [webView stringByEvaluatingJavaScriptFromString:touchhold]; [touchhold release]; } }

普通にアクションシートを出してあげる

これはもう簡単で、こんな感じ。

1
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ if([request.URL.scheme isEqualToString:@"nv"] && [request.URL.host isEqualToString:@"taphold"] ){ [_actionSheetTmpUrl release]; [_actionSheetTmpTitle release]; _actionSheetTmpUrl = [[_webView stringByEvaluatingJavaScriptFromString:@"NVTimer.currentUrl"]retain]; _actionSheetTmpTitle = [[_webView stringByEvaluatingJavaScriptFromString:@"NVTimer.currentTitle"]retain]; NSString \*title = [NSString stringWithFormat:@"\\"%@\\"\\n%@",_actionSheetTmpTitle,_actionSheetTmpUrl]; UIActionSheet \*sheet = [[UIActionSheet alloc] initWithTitle:title delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"リンク先を開く",@"リンクをコピー",nil]; [sheet showInView:[UIApplication sharedApplication].keyWindow]; [sheet release]; return NO; } return YES; }

_actionSheetTmpTitle と _actionSheetTmpUrl にそれぞれリンクのテキストとURLを保存しているので、アクションシートからのdelegateがきたらこれの情報を元に処理してあげればOK!

というわけで、簡易的なリンク長押しハックでした。 精度的には(人によるけど)50%ぐらいで、モックアップ程度ならこれでいいかな〜という感じです。 売り物だとちょっと精度が低い。。。かもしれません。