ポップアップウィンドウをUIWebViewで使う

Posted by Yoshimasa Niwa on 02/06, 2009

UIWebViewはiPhone SDKのかなり重要なUIKitのクラスです。 ご存知の通りSafariが丸ごと入ってますが、しかし、ウィンドウを開く、ポップアップするというイベントは無効にされています。

<a href="somehere" target="_blank" />Open this link in new window</a>

たとえば、このようなリンクがUIWebViewのなかで表示されてユーザがクリックしたとしても、何も起きません。 勿論、UIWebViewはかなり高度に抽象化されていて、いくつかのメソッドを呼ぶ事はできます。 そこで、完璧ではないのですがある程度この問題を解決する手段があります。 鍵となるメソッドは次のものです。

language:objc
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
- (void)webViewDidFinishLoad:(UIWebView *)webView
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script

すべてのポップアップイベントを奪う

まず始めに、表示しているウェブページのすべてのウィンドウを開くイベントを奪います。 どうやるのかというと、JavaScriptで解決します。 手法は、まずすべての開くイベントを奪って開くべきURLを特別なものにして、さらにObjective-C側でその特別なURLを再処理して自前でウィンドウを開きます。

language:objc
- (void)webViewDidFinishLoad:(UIWebView *)webView {
  [webView stringByEvaluatingJavaScriptFromString:/* JavaScriptの入った文字列 */];
}

次のJavaScriptをstringByEvaluatingJavaScriptFromStringに渡します。

language:javascript
var tags = document.getElementsByTagName("a");
for(var i=0; i < tags.length; i++) {
  var tag = tags[i];
  var t = tag.getAttribute("target");
  var h = tag.getAttribute("href");
  if(/* targetとhrefを確認 */) {
    tag.setAttribute("target", "");
    tag.setAttribute("href", /* href属性から特別なURLを作成 */);
  }
}

webViewDidFinishLoadが呼ばれたときにJavaScriptを実行してすべてのアンカータグを奪ってターゲット属性を削除してhrefに特別なのもに置き換えます。 さらに、フォームとJavaScriptからのイベントも奪います。

language:javascript
var tags = document.getElementsByTagName("form");
for(var i=0; i < tags.length; i++) {
  var tag = tags[i];
  var submit = tag.submit;
  tag.submit = function() {
    var t = tag.target;
    var a = tag.action;
    if(/* targetとactionを確認 */) {
      tag.target = "";
      tag.action = /* action属性から特別なURLを作成  */
    }
    return submit.apply(this, arguments);
  };
}

このコードは多くの状況で動くのですが、すべての状況での動作は難しいです。特にsumitイベントのリスナーが存在する場合などです。

language:javascript
window.open = function(url) {
  if(/* urlを確認 */) {
    var t = document.createElement("a");
    t.setAttribute("href", /* 特別なurlを作成 */);
    var e = document.createEvent("MouseEvent");
    e.initMouseEvent("click");
    t.dispatchEvent(e);
  }
};

このコードはかなり変なのです。window.openが呼ばれた際に画面の裏で見えないアンカータグをつくりそれに特別なURLを設定してHTTPリクエストを発生させます。 このリクエストをObjective-C側で受け取ります。

ポップアップイベントをObjective-Cで受け取る

これで、Objective-C側でイベントを受け取る事ができるようになりました。 UIWebViewのdelegateでポップアップイベントを受け取り処理します。

language:objc
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
  if(navigationType == UIWebViewNavigationTypeLinkClicked || navigationType == UIWebViewNavigationTypeFormSubmitted) {
    NSURL *url = [request URL];
    if(/* URLが特別なものか確認 */) {
      NSString *urlstr = /* 特別なURLから普通のURLに戻す */;
      NSMutableURLRequest *req = [request mutableCopyWithZone:nil];
      [req setURL:[NSURL URLWithString:urlstr]];

      if(/* ポップアップは閉じているか? */) {
        /* ポップアップを開く */
      }
      [popupWebView loadRequest:req];
      return NO;
    }
  }
  return YES;
}

この凄く長い名前のdelegateですべてのHTTPリクエストを取得することができます。 まずリクエストのURLがJavaScriptでつくった特別なものかをチェックして、もしそうなら普通のURLの戻してNSURLRequestを作成し、ポップアップするUIWebViewに渡します。 これらのコードによってUIWebViewでポップアップを実現できます。

特別なURLを作成する

この問題はちょっと難しいです。なぜなら、HTTPリクエストのベースURLを保持する必要があるからです。 URLの書き換えても安全な部分、たとえば、スキーマやホスト名などですが、これらを書き換えるとベースURLが失われてしまい、webView:shouldStartLoadWithRequest:navigationTypeの中でポップアップの為のURLを作成することができなくなってしまいます。 そこで、一案ではあるのですが、URLの最後に特別なHashを付与することで一応の解決をみます。 あまり安全ではないのですが、多くの状況では安全です。

language:javascript
function makeSpecialURL(url) {
  return url + (url.match(/#/) ? "_open" : "#open");
}

そして、webView:shouldStartLoadWithRequest:navigationTypeのなかで追加したHashを削除します。

language:objc
if([[url fragment] hasSuffix:@"open"]) {
  NSString *urlstr = [[url absoluteString] substringToIndex:[[url absoluteString] length] - 5];
  ...
}

その他の手法について

この問題は多分ドキュメントにのっていないAPIかdelegate(webView:createWebViewWithRequest:)で解決できます。 しかし、iPhone SDKの中だけでは、このJavaScriptを使う手法以外にはよい解決方法はないでしょう。 あまりよい実装ではないですが、iPhoneアプリケーションのなにかの助けになればと思います。

Share

Comments

  • Dan Grigsbyさんのコメント 02/17, 2009
    Very clever! Thanks for posting this to iPhoneFlow too.
  • Mikeさんのコメント 06/18, 2009
    Thanks for the info. Just FYI - the first snippet, using "context.document.getElementsByTagName()" didn't work for me and took a while to debug without a proper Javascript debugging environment on the phone. Just change it to "document.getElementsByTagName()" and it works fine.
  • Yoshimasa Niwaさんのコメント 06/19, 2009
    Hi Mike, Exactly! I forgot strip "context." from the snippet. actually I had to use this hack inside frame so I'd add context var to change target frame.
  • RokaJokaさんのコメント 06/19, 2009
    What do you mean by?

    "/* is popup window close? */"
  • Yoshimasa Niwaさんのコメント 06/19, 2009
    Hi RokaJoke, It's my mistake... I cleaned up this snippet!
  • RokaJokaさんのコメント 06/19, 2009
    You mean this right?

    /* Is the popup window not already open? */

    //Open a popup window
  • Yoshimasa Niwaさんのコメント 06/20, 2009
    Hi RokaJoka, Yes. Nothing special there, you do just open a popup window!
  • Baskaranさんのコメント 07/09, 2009
    Do you have any idea on how to identify and navigate to individual pages in a UIWebView loaded with a PDF or word or pages? I tried windows.scrollByPage() funciton - but it does not work.
  • Yoshimasa Niwaさんのコメント 07/15, 2009
    Hi Baskaran, I think PDF or Word file is not rendered by WebKit directly and also, it is not HTML file. you can't use any JavaScript with them. Some hidden API may navigate the pages inside PDF or Word though, I have no idea about it.
  • Shinさんのコメント 12/03, 2009
    I am actually loading some text from a webpage and then the image associated with it in UIWebView . I have seen many news apps where u can click on the image and a larger zoomed images pops open. Any ideas how I can implement that. I have tried scalestofit but that isn't something which i would like to use since it makes the text very tiny to read. Also I'm not loading the whole webpage I am only loading some text and image so please give me a solution on how to open up a larger version of the image when a user clicks on the small thumbnail image.
  • sivaさんのコメント 07/01, 2010
    Hi

    i need the favour in Webview,actually im using the webpage of my site to login,after logging in the user have to take to my application.i mean have to display the next page in another view.where it has our toolbar and bottom bar.when the user enters inside the next page he have to be logged .there is any way to solve this issue.


    Thanks in advance...
  • Sgさんのコメント 07/01, 2010
    Hi, this looks like just what I need; I just want all urls to open regardless of whether they're new pages or not. But this code is full of /* ... */ comments and I have no idea how to do all this JavaScript code - I work in obj-c. Can I humbly request someone post a completed implementation with the comments replaced? Thanks for this.
  • Maheshさんのコメント 11/27, 2010
    Hi,
    Thanks for the info.
    I have problem on showing Menu list button ( when you click this button, list of Menu will be shown) of a webpage in UIWebView. That is, if I see the web page in iPad safari the menu list button is aligned properly as we see in the desktop browser. But when I load this page in UIWebView, the page loaded correctly, but the Menu List button ( which might be Javascript) is not shown properly and placed one pixel down from the actual position. I should not change anything in the website for this issue. I need to solve this problem only in the native app,( i.e., only with UIWebView controller). Is it possible to solve this problem? Please support me on this. I have lot of issue like this when I show the Webpages in the UIWebView controller.
  • Kenneth Lewisさんのコメント 08/08, 2011
    How can you enable the popup when the very first page is the popup like for a wireless router like Netgear. The only way I am able to get past it is by adding authentication to the loadRequest:forHTTPHeaderField:@"Authentication" but then the wireless router uses frames and so nothing displays after that. Now when I enable popups for Safari itself in settings then the popup shows and Safari does handle the frames correctly so how can we get the same in UIWebView as Safari?

    Thanks,

    Kenneth
  • Androidさんのコメント 09/22, 2015
    hello friends i have seen a good example ....

    http://androidexample.com/Show_Loader_To_Open_Url_In_WebView__-_Android_Example/index.php?view=article_discription&aid=125&aaid=145
  • Android Exampleさんのコメント 05/07, 2016
    Hi dude, i have also find out one good example
    <a href="http://androidexample.com/Show_Loader_To_Open_Url_In_WebView__-_Android_Example/index.php?view=article_discription&aid=125&aaid=145"> Show Loader To Open Url In WebView</a>
  • Android Exampleさんのコメント 09/22, 2016
    Hi dude, i have also find out one good example
    <a href="http://androidexample.com/Show_Loader_To_Open_Url_In_WebView__-_Android_Example/index.php?view=article_discription&aid=125&aaid=145">
    Show Loader To Open Url In WebView - Android Example</a>
お名前
メールアドレス
(任意)
ウェブサイト
(任意)
コメント