ぽんぽこ日記

プログラミング、読書、日々の生活

UIWebView内の画面遷移をフックする 応用編

当ブログのアクセスログを見ていると、shouldStartLoadWithRequestについて書いた「UIWebView内の画面遷移をフックする」が圧倒的に人気コンテンツであるようです。

そこで、応用編として、アプリ内のUIWebView画面で、webサイト上のファイルへのダウンロードリンクをクリックするとそのアプリのドキュメントフォルダに直接DLするような仕組みを作ってみます。

アイデアとしては、shouldStartLoadWithRequestのrequestパラメータに含まれ るURLを抽出し、事前にそのURLに対してHEADリクエストを送って、DL可能なファイルへのURLかどうか先読みし、DL可能ならそのままDLし、DL対象でない普通のhtmlページリンクの場合はUIWebViewに制御を返すというものです。

f:id:ponpoko1968:20121125181140j:plain

リンク先の先読みとDLには、おなじみASIHttpRequestを使います。(開発は終了してしまいましたが。。)

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
Log( @"manualLoading=%@",manualLoading ? @"YES" : @"NO" );
if( manualLoading ){        // 先読み前
  NSURL* url = [request URL];
  // HEADメソッドを使用
  ASIHTTPRequest *req = [[ASIHTTPRequest requestWithURL:url] HEADRequest];

  req.tag = kRequestForHeader;
  [req setDidFinishSelector:@selector(requestDone:)];
  [req setDidFailSelector:@selector(requestWentWrong:)];
  [req setDelegate:self];

  [self.queue addOperation:req];
  [self.queue go];

  return NO;
}else{          // 先読み後
  return YES;
}
return NO;
}

UIWebViewのdelegateであるHTWebViewControllerは、shouldStartLoadWithRequestを送られたら、いったんすべての操作に対してNOを返します。

同時に、ASIHttpRequestオブジェクトを作って、requestパラメータに含まれるURLをコピーします。 HEADRequestはhttp内部のパラメータはそのままに、HEADメソッドを送出するインスタンスを生成するメソッドです。

あらかじめ

- (void)viewDidLoad
{
....
    self.queue.requestDidReceiveResponseHeadersSelector = @selector(request:DidReceiveResponseHeaders:);
....
}

で、ASINetworkQueueがレスポンスヘッダを受け取ったときのデリゲートメッセージ(request:DidReceiveResponseHeaders:)を受け取るようにしておくと、ヘッダ情報を取得できます。取得したヘッダ情報の中身を見て、ダウンロードするのか、 UIWebViewにページをリクエストさせるのかを判断します。

-(void)request:(ASIHTTPRequest*) request DidReceiveResponseHeaders:(NSDictionary*)responseHeders
{
  ....
  // Content-TypeがPDFファイル
  if( ! NSEqualRanges([[responseHeders objectForKey:@"Content-Type"] rangeOfString:@"application/pdf" options:NSAnchoredSearch|NSCaseInsensitiveSearch],NSMakeRange(NSNotFound,0))){
    ASIHTTPRequest* req = [ASIHTTPRequest requestWithURL:[request url]];
    NSArray* paths= NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
// ダウンロード先フォルダをサンドボックスコンテナに設定、ファイル名はUUIDにする
    [req setDownloadDestinationPath:[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.pdf",[self newUUIDString]] ]];


    [req setDidFinishSelector:@selector(downloadDone:)];
    [req setDidFailSelector:@selector(downloadFailed:)];
    [req setDelegate:self];


    [self.queue addOperation:req];
    [self showProgressView];
    [self.queue go];

    return;
  }

  // 通常のWebページなど,UIWebViewに処理させたい場合
  if( [@"text/html" isEqualToString:[responseHeders objectForKey:@"Content-Type"]] ){
    NSURLRequest* requestx = [NSURLRequest requestWithURL:[request url]];
// 無限ループ対策
    manualLoading = NO;
    [self.webView loadRequest:requestx];
    return;
  }
}

上のメソッド内で、このビューコントローラからUIWebViewに対してloadRequest:を送って明示的にページをロードさせる場合も、webView:shouldStartLoadWithRequest:navigationType:が送られてくるため、無限ループに陥らないようmanualLoadingというフラグで処理しています。よりきめ細かく状態を制御するならNSMutableURLRequestインスタンスを生成してヘッダ情報を追加するなどすれば良いでしょう。

実用で使う上では、たとえばファイルをダウンロードするにはログイン等の認証が必要だったりするサイトも想定されるなどあるので、cookieをはじめとする他のヘッダ情報も各種リクエストオブジェクトの間でコピーして引き継ぐなど、より細かい処理が必要です。(取得先に置かれていたデータをダウンロードした後の取り扱いでは、セキュリティにご注意ください。)ともあれ、UIWebViewの挙動の一部を横取りすることで、ファイルをアプリのサンドボックスコンテナにダウンロードできます。

サンプルプログラム全体を

https://github.com/ponpoko1968/HTTPDownloaderTest

に置いたので、適当にforkして改造して使って下さい。