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.
Maybe you should evaluate the JavaScript in isolated world. For iOS you should evaluate the js code in defauliClientWorld.