iosのWKWebViewでPDFのダウンロード&他アプリで開く (Swift3)

pdfなどをWebviewで表示する代わりにダウンロードしてiBooksを開くということをやりたい








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の上に重なるような形で表示されます。

UIDocumentInteractionController




一連のソースサンプル




// 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にアクセスしたらダウンロード)でサーバ側を実装すること。

2017年1月20日金曜日