WKWebViewでPDFをダウンロードする
色々試してみた結果ダウンロードファイルの取得は直接ファイルURLを指定するかGETでないといけない
Web画面読み込み時に
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
上記の関数内で特定のパスのときのみローカルにファイルとしてダウンロード します。WKNavigationDelegateを実装する必要があります。
ヘッダーを見ることもできますが不要なリクエストを飛ばすことになるのでURLなどをみて判断したほうがいいかと(今回はファイルは必ずURLにdownloadという文字が入ってくると想定します)
if let url = navigationAction.request.url?.absoluteString {
if url.contains("download") {
// Data形式でURLを取得
let data = try! Data(contentsOf: URL(string: url)!)
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
let fileName = "temp.pdf"
self.dlFilePath = documentDirectory?.appendingPathComponent(fileName)
try! data.write(to: self.dlFilePath!, options: .atomic)
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
}
Data形式指定したURLの中身を取得。
パスを指定してData.writeでファイルの作成・書き込みをおこなっています。
ファイルを表示せず、ダウンロードのみをおこなうため decisionHandler(WKNavigationActionPolicy.cancel) でページの読み込みを中断しています。
以上で xxxx/Document/ へ temp.pdfファイルが作成されます。
UIDocumentInteractionControllerを使用してデータを共有する
基本的にアプリ内で作成したデータを別アプリで使用すると行ったことができないので別のアプリで閲覧するためにファイルのコピーが必要になります。
そのための仕組みが用意してあります。
let dlFilePath = URL(string: "先ほど作成したファイルのパス") var doc = UIDocumentInteractionController() doc = UIDocumentInteractionController(url: dlFilePath!) doc.presentOpenInMenu(from: .zero, in: self.view, animated: true) messageLabel.isHidden = true
以下のようなViewの上に重なるような形で表示されます。
一連のソースサンプル
// MARK: - webView Delegate
/// Web画面遷移時処理
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
print( navigationAction.request )
if let url = navigationAction.request.url?.absoluteString {
// pdfのダウンロード
if url.contains("download") {
// ダイアログなどを表示する
OperationQueue().addOperation({ () -> Void in
let data = try! Data(contentsOf: URL(string: url)!)
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
let fileName = "temp.pdf"
self.dlFilePath = documentDirectory?.appendingPathComponent(fileName)
try! data.write(to: self.dlFilePath!, options: .atomic)
DispatchQueue.main.async(
execute: { // マインスレッドでないと落ちる
self.doc = UIDocumentInteractionController(url: self.dlFilePath! )
self.doc.presentOpenInMenu(from: .zero, in: self.view, animated: true)
// ダイアログを閉じる処理など
} )
})
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
}
decisionHandler(WKNavigationActionPolicy.allow)
}
メインスレッドでファイルの書き込みなどをおこなうと固まってしまうので別スレッドでおこなっています。ダイアログなどを表示するのがいいかと
UIDocumentInteractionControllerをメインスレッド以外で使用するとエラーが発生するためメインスレッドで実行しています。
ファイル名はRestっぽいURLを元に作成するような設計にしておけばいい感じに作れると思います。
UIDocumentInteractionControllerはローカルで持つとメモリが解放される恐れがあるようなのでクラスのメンバなどで持たせておきます。
ファイルのダウンロード後は元のファイルは削除した方がいいかも
UIDocumentInteractionControllerについての詳細は以下
https://developer.apple.com/jp/documentation/DocumentInteraction_TopicsForIOS.pdf
その他メモ
最初はPOSTでCSRFトークン付きのダウンロードにしていたがwebView上で表示までは確認できたが、うまく処理することができなかった。(Safariならいけるがやり方がわからなかった)
特に理由がなければGET(URLにアクセスしたらダウンロード)でサーバ側を実装すること。