Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Download embedded PDF loaded in WKWebView

While loading HTML5 page from url I'm getting pdf somewhere in that page and I have to download that pdf or save it as base64.

This is where the pdf is in the HTML code. I cannot simply hit the 'src' URL and get the pdf.

< embed width="100%" height="100%" name="plugin" id="plugin" src="https://myurl.com/fileToOpen.pdf” type="application/pdf" internalinstanceid="8" title="">

Any JS which can help me get base64 string of that or any other method to download?

like image 753
Nitesh Avatar asked Feb 02 '18 17:02

Nitesh


3 Answers

This question asked sometimes back, But if someone looking for swift solution with WKWebView to download .pdf or any file on File manager, this is how I ended-up

class WebPortalVC: UIViewController, WKNavigationDelegate,WKUIDelegate, UIDocumentInteractionControllerDelegate,URLSessionDownloadDelegate {

override following function, that will intercept url, in our case we check ulr ending with .pdf and .csv and redirect to open with file manger view. which enable view file, download and save on device storage, airdrop or share with other apps

just add following functions and check.

 func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {

        print("fileDownload: check ::  \(url)")

        let extention = "\(url)".suffix(4)

        if extention == ".pdf" ||  extention == ".csv"{
            print("fileDownload: redirect to download events. \(extention)")
            DispatchQueue.main.async {
                self.downloadPDF(tempUrl: "\(url)")
            }
            decisionHandler(.cancel)
            return
        }

    }

    decisionHandler(.allow)
}

func downloadPDF(tempUrl:String){
    print("fileDownload: downloadPDF")
    guard let url = URL(string: tempUrl) else { return }
    let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
    let downloadTask = urlSession.downloadTask(with: url)
    downloadTask.resume()
    //showHUD(isShowBackground: true); //show progress if you need
}
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
    print("fileDownload: documentInteractionControllerViewControllerForPreview")
    return self
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    // create destination URL with the original pdf name
    print("fileDownload: urlSession")
    guard let url = downloadTask.originalRequest?.url else { return }
    print("fileDownload: urlSession \(url)")
    let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let destinationURL = documentsPath.appendingPathComponent(url.lastPathComponent)
    // delete original copy
    try? FileManager.default.removeItem(at: destinationURL)
    // copy from temp to Document
    do {
        try FileManager.default.copyItem(at: location, to: destinationURL)
        myViewDocumentsmethod(PdfUrl:destinationURL)
        print("fileDownload: downloadLocation", destinationURL)
        DispatchQueue.main.async {
            NBMaterialToast.showWithText(self.view, text: "Download Completed", duration: NBLunchDuration.long)
        }
    } catch let error {
        print("fileDownload: error \(error.localizedDescription)")
    }
   // dismissHUD(isAnimated: false); //dismiss progress
}
func myViewDocumentsmethod(PdfUrl:URL){
    print("fileDownload: myViewDocumentsmethod \(PdfUrl)")
    DispatchQueue.main.async {
        let controladorDoc = UIDocumentInteractionController(url: PdfUrl)
        controladorDoc.delegate = self
        controladorDoc.presentPreview(animated: true)
    }
}
like image 174
UdayaLakmal Avatar answered Nov 10 '22 03:11

UdayaLakmal


Update

From the Docs they say

The Fetch API provides an interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest

You can also use the following string to get the base64 String from the WKWebview

 let s = "path = document.getElementById(\"plugin\").src\n" +
        "\n" +
        "fetch(path).then(function (response) {\n" +
        " response.body.getReader().read().then(function(result) {\n" +
        " return btoa(String.fromCharCode.apply(null, result.value));\n" +
        " }).then(function(b64) {\n" +
        " window.webkit.messageHandlers.myInterface.postMessage(b64);\n" +
        " });\n" +
        "});"

both fetch and xmlhttp works async.. all you need to do is wait when the processing gets completed pass it to the Swift using javascript's bridge to ios (WKScriptMessageHandler)

Use the following code to get the base64 string from javascript to Swift. I am using WKScriptMessageHandler to get the callback from Javascript when the base64 string is ready to be consumed. In the String s you just need to pass the url of the pdf and it will do a ajax request to get the pdf file and then convert it to base64 string.

import UIKit
import WebKit
class ViewController: UIViewController {
    @IBOutlet weak var btnPDF: UIButton!
    @IBOutlet weak var webViewParentView: UIView!
    var activityIndicator: UIActivityIndicatorView?
    var webView: WKWebView!
    @objc func didSelect(_ sender: UIView){
        let s="var xhr = new XMLHttpRequest();\n" +
            "xhr.open(\'GET\', \"https://codingexceptions.com/wkwebview/dummy.pdf\", true);\n" +
            "\n" +
            "xhr.responseType = \'arraybuffer\';\n" +
            "\n" +
            "xhr.onload = function(e) {\n" +
            " if (this.status == 200) {\n" +
            " var uInt8Array = new Uint8Array(this.response);\n" +
            " var i = uInt8Array.length;\n" +
            " var binaryString = new Array(i);\n" +
            " while (i--)\n" +
            " {\n" +
            " binaryString[i] = String.fromCharCode(uInt8Array[i]);\n" +
            " }\n" +
            " var data = binaryString.join(\'\');\n" +
            "\n" +
            " var base64 = window.btoa(data);\n" +
            "\n" +
            "window.webkit.messageHandlers.myInterface.postMessage(base64);" +
            "\n" +
            " }\n" +
            "};\n" +
            "\n" +
        "xhr.send();\n"
        webView.configuration.userContentController.add(self, name: "myInterface")
        webView?.evaluateJavaScript(s, completionHandler: {(string,error) in
            print(error ?? "no error")
        })
    }
    func setupWebView(){
        webView = WKWebView.init(frame: CGRect(x: 0, y: 0, width: webViewParentView.frame.width, height: webViewParentView.frame.height))
        webView.navigationDelegate = self
        webViewParentView.addSubview(webView)
        activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
        activityIndicator?.center = self.view.center
        self.view.addSubview(activityIndicator!)
        webView.load(URLRequest(url: URL(string: "https://codingexceptions.com/wkwebview/index.php")!))
        activityIndicator?.startAnimating()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        btnPDF.addTarget(self, action: #selector(self.didSelect(_:)), for: .touchUpInside)

    }
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
         setupWebView()
    }
}
extension ViewController: WKScriptMessageHandler{
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
         print("Message received: \(message.name) with body: \(message.body)")
    }
}
extension ViewController: WKNavigationDelegate{
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        self.activityIndicator?.stopAnimating()
        self.activityIndicator?.removeFromSuperview()
        self.activityIndicator = nil
    }
}

Update: To get the source from the embed tag as pointed in @Tarun's answer

just put the below line in the starting of string variable s and pass the url in xhr.open

var url = document.getElementById("plugin").src
like image 3
Sahil Avatar answered Nov 10 '22 03:11

Sahil


PS: Using answer as comments, as I need formatting

You should execute the below JavaScript in the webview

path = document.getElementById("plugin").src

fetch(path).then(function (response) {
    response.body.getReader().read().then(function(result) {
        return btoa(String.fromCharCode.apply(null, result.value));
    }).then(function(b64) {
        window.pdf_data = b64;
    });
});

Then you can execute another query to access the window.pdf_data assuming get return value from a javascript execution is possible?

like image 2
Tarun Lalwani Avatar answered Nov 10 '22 03:11

Tarun Lalwani