3

I need to implement file downloading in WKWebView.

My example implementation:

@available(iOS 14.5, *)
func webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) {
    download.delegate = self
}
@available(iOS 14.5, *)
func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) {
    download.delegate = self
}
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    if let mimeType = navigationResponse.response.mimeType {
        if let url = navigationResponse.response.url {
            if #available(iOS 14.5, *) {
                decisionHandler(.download)
            } else {
                let ext = getExtension(mimeType)
                let fileName = "file." + ext;
                downloadData(fromURL: url, fileName: fileName) { success, destinationURL in
                    if success, let destinationURL = destinationURL {
                        self.openDownloadFile(destinationURL)
                    }
                }
                decisionHandler(.cancel)
            }
            return
        }
    }
    decisionHandler(.allow)
}

private func downloadData(fromURL url: URL,
                          fileName: String,
                          completion: @escaping (Bool, URL?) -> Void) {
    webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
        let session = URLSession.shared
        session.configuration.httpCookieStorage?.setCookies(cookies, for: url, mainDocumentURL: nil)
        let task = session.downloadTask(with: downloadURL) { localURL, _, error in
            if let localURL = localURL {
                let destinationURL = self.moveDownloadedFile(url: localURL, fileName: fileName)
                completion(true, destinationURL)
            } else {
                completion(false, nil)
            }
        }
        task.resume()
    }
}

private func moveDownloadedFile(url: URL, fileName: String) -> URL {
    let tempDir = NSTemporaryDirectory()
    let destinationPath = tempDir + fileName
    let destinationURL = URL(fileURLWithPath: destinationPath)
    try? FileManager.default.removeItem(at: destinationURL)
    try? FileManager.default.moveItem(at: url, to: destinationURL)
    return destinationURL
}

private func openDownloadFile(_ fileUrl: URL) {
    DispatchQueue.main.async {
        let controller = UIActivityViewController(activityItems: [fileUrl], applicationActivities: nil)
        controller.popoverPresentationController?.sourceView = self.view
        controller.popoverPresentationController?.sourceRect = self.view.frame
        controller.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem
        self.present(controller, animated: true, completion: nil)
    }
}

It works for iOS >= 14.5 and when response is a File. But when url is blob (for example "blob:http://example.com/bd0ae6c8-796c-488e-b691-cee86fa72f04") it does not work for iOS < 14.5.

return unsupported URL error.

I tried to handle and download blob url the same as in android (Example for android):

private func downloadBlobFile(blobUrl: String, fileName: String, mimeType: String) {
    webView.evaluateJavaScript("javascript: var xhr = new XMLHttpRequest();" +
        "xhr.open('GET', '" + blobUrl + "', true);" +
        "xhr.setRequestHeader('Content-type','" + mimeType + "');" +
        "xhr.responseType = 'blob';" +
        "xhr.onload = function(e) {" +
        "    if (this.status == 200) {" +
        "        var blob = this.response;" +
        "        var reader = new FileReader();" +
        "        reader.readAsDataURL(blob);" +
        "        reader.onloadend = function() {" +
        "            base64data = reader.result;" +
        "            window.webkit.messageHandlers.callback.postMessage(base64data);" +
        "        }" +
        "    }" +
        "};" +
        "xhr.send();")
}

This code works on Android fine, but on iOS xhr.onload not called. It call xhr.onerror with status 0 and empty responseText.

Also I tried to remove "blob:" from blob url. "blob:http://example.com/bd0ae6c8-796c-488e-b691-cee86fa72f04" -> "http://example.com/bd0ae6c8-796c-488e-b691-cee86fa72f04" and download it with URLSession. But it get me corrupt file.

And I have tried to add LSApplicationQueriesSchemes blob, but it does not help me. enter image description here

runia
  • 370
  • 6
  • 19
  • What is the error you have received? I see that the UrL is http. Did you allow arbitrary loads to make non TLS connections? – CloudBalancing Sep 03 '21 at 02:18
  • @CloudBalancing , yes I did it. I updated my question. If you asking about XMLHttpRequest, then it calls onerror with status 0, without responseText. – runia Sep 03 '21 at 06:46
  • I meant the error you get when trying to use the url session to download the blob. You said that the code for iOs >= 14.5 works. Meaning the url session fails? – CloudBalancing Sep 03 '21 at 15:28
  • On iOS >= 14.5 it works because Apple handle it and download automatically with decisionHandler(.download) and download delegate. But on iOS <14.5 need manually handle and download file. When I try download blob with URLSession I get error unsupported URL. – runia Sep 04 '21 at 04:32
  • Ok. I should have put a bit more read into it, I think you will have to handle some JS within the web page. Seems like WKWebView didn't support blobs out of the box until recently- https://stackoverflow.com/questions/61702414/wkwebview-how-to-handle-blob-url. You will have to convet to data uri - https://stackoverflow.com/questions/14952052/convert-blob-url-to-normal-url/16179887#16179887 – CloudBalancing Sep 04 '21 at 07:27
  • Sorry, this method (convert data uri in js) does not suitable for me. Now js get base64 from server and convert it to blob and download it with FileSaver. And I only need implement the solution in iOS app and Android App. In Android app download app with js injection (XMLHttpRequest). But on iOS this method does not work. – runia Sep 04 '21 at 12:19
  • @runia got any solution yet? – Amir Khan Jun 06 '22 at 11:10
  • @runia have you found a solution? – Pramod Tapaniya Nov 28 '22 at 13:44

1 Answers1

0

Maybe you should evaluate the JavaScript in isolated world. For iOS you should evaluate the js code in defauliClientWorld.

xxycode
  • 1
  • 1
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 11 '22 at 05:17