8

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?

pacification
  • 5,838
  • 4
  • 29
  • 51
Nitesh
  • 1,564
  • 2
  • 26
  • 53

4 Answers4

5

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)
    }
}
UdayaLakmal
  • 4,035
  • 4
  • 29
  • 40
3

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
Sahil Manchanda
  • 9,812
  • 4
  • 39
  • 89
  • @Nitesh glad this worked. Is this solves your current requirement? – Sahil Manchanda Feb 10 '18 at 11:23
  • Yes. Also can you please help me out more with the Tarun's solution too. Or if possible update the answer with that too. – Nitesh Feb 10 '18 at 11:26
  • @SahilManchanda, This was the part I didn't know since it involves iOS code. Thanks for the update :-) – Tarun Lalwani Feb 11 '18 at 05:31
  • @SahilManchanda Can u please help. Now all sudden above script stopped working. only thing change in the website js is internalinstanceid="4" previous it was 8. – Nitesh Jul 02 '18 at 10:12
  • @Nitesh hi, can you share the url that you are trying. or you can attach safari developer controls with simulator and check on console what error you are getting. – Sahil Manchanda Jul 02 '18 at 11:19
  • I don't know how after after adding a completionHandler it started working. Thanks for help @SahilManchanda – Nitesh Jul 02 '18 at 11:42
  • @SahilManchanda Can you help me with this https://stackoverflow.com/questions/52928183/get-page-loaded-event-on-webview – Nitesh Oct 22 '18 at 11:23
2

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?

Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • The above code didn't work Or I didnt understood it how to get it work. Just for example this is how I get base64 String for image, it it helps you guide me. var c = document.createElement('canvas'); var ctx = c.getContext('2d'); ctx.drawImage(document.getElementById('captcha_id'), 100, 40); var value = c.toDataURL(); value.split(',')[1]; – Nitesh Feb 05 '18 at 16:01
  • How do you access value from the swift code? Sorry I am not a IOS dev – Tarun Lalwani Feb 05 '18 at 16:05
  • 'window.pdf_data = b64;' This b64 is string here? – Nitesh Feb 05 '18 at 16:07
  • Yes this, will contain the base64 string. But remember this is async code. SO the value won't be available immediately – Tarun Lalwani Feb 05 '18 at 16:08
  • I'm also trying this method with the answer provided by Sahil. – Nitesh Feb 09 '18 at 15:07
-2

Are you looking to download the PDF to your iPhone or to your Mac? Generally, it's not possible to download PDFs directly to your iPhone because the iPhone doesn't natively have the capability to store PDFs. You'd need to have iBooks or iCloud Drive and then open the PDF in another window, followed by manually downloading it. There would still need to be user-interaction before you download the PDF, meaning the user would have to approve the download. Direct download via the injection of JavaScript into a WKWebView instance is is not possible.

ch1maera
  • 1,369
  • 5
  • 20
  • 42
  • 1
    We can also store it in Documents if I’m not wrong. About direct download, yeah even I started to think it’s not possible. Just wanted to give a try as I’m not gd with JS. – Nitesh Feb 06 '18 at 02:43
  • Documents, you mean on a Mac? – ch1maera Feb 06 '18 at 02:45
  • There is no documents app on the iPhone. You could try with the Google Drive app, but it would be the same process as using iBooks or iCloud Drive. – ch1maera Feb 06 '18 at 02:47
  • 1
    Its possible to download pdf directly to iPhone Files. – Abin Baby Aug 07 '18 at 09:21