下载在 WKWebView 中加载的嵌入式 PDF

Download embedded PDF loaded in WKWebView

从 url 加载 HTML5 页面时,我在该页面某处获取 pdf,我必须下载该 pdf 或将其另存为 base64

这是 pdf 在 HTML 代码中的位置。我不能简单地点击 'src' URL 并获取 pdf。

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

任何 JS 可以帮助我获取 base64 字符串或任何其他下载方法?

PS: 使用答案作为评论,因为我需要格式化

您应该在 webview

中执行以下 JavaScript
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;
    });
});

然后您可以执行另一个查询来访问 window.pdf_data 假设从 javascript 执行中获取 return 值是可能的?

您是要将 PDF 下载到您的 iPhone 还是 Mac?通常,无法将 PDF 直接下载到您的 iPhone,因为 iPhone 本身不具备存储 PDF 的功能。您需要有 iBooks 或 iCloud Drive,然后在另一个 window 中打开 PDF,然后手动下载它。下载 PDF 之前仍然需要用户交互,这意味着用户必须批准下载。通过将 JavaScript 注入到 WKWebView 实例中直接下载是不可能的。

更新

Docs他们说

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

您还可以使用以下字符串从 WKWebview 中获取 base64 字符串

 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" +
        "});"

fetch 和 xmlhttp 都是异步工作的。您需要做的就是等待处理完成后使用 javascript 到 ios 的桥将其传递给 Swift( WKScriptMessageHandler)

使用以下代码获取从javascript到Swift的base64字符串。 我正在使用 WKScriptMessageHandler 从 Javascript 获取回调,当 base64 字符串准备好被使用时 。在 String 中,你只需要传递 pdf 的 url 它会做一个 ajax 请求来获取 pdf 文件,然后将其转换为 base64 字符串。

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
    }
}

更新:按照@Tarun 的回答

从嵌入标签中获取源代码

只需将下面的 行放在字符串变量 s 的开头,并在 xhr.open

中传递 url
var url = document.getElementById("plugin").src

这个问题有时会反问, 但是,如果有人正在寻找 swift 解决方案 WKWebView 以下载 .pdf 或文件管理器上的任何文件,这就是我结束的方式-向上

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

覆盖以下函数,它将拦截 url,在我们的例子中,我们检查以 .pdf 和 .csv 结尾的 ulr 并重定向以使用文件管理器视图打开。启用查看文件、下载并保存到设备存储、空投或与其他应用程序共享

只需添加以下功能并检查即可。

 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)
    }
}