下载 WKWebView 中加载的嵌入式 PDF

Posted

技术标签:

【中文标题】下载 WKWebView 中加载的嵌入式 PDF【英文标题】:Download embedded PDF loaded in WKWebView 【发布时间】:2018-02-02 17:30:53 【问题描述】:

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

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

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

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

【问题讨论】:

你能在 webview 中执行一段 javascript 代码吗? @TarunLalwani 是的。 你能解释更多吗?或提供您尝试加载的网页的图片? @Nitesh 请看看我的回答 【参考方案1】:

这个问题有时会被问到, 但是,如果有人使用 WKWebView 寻找 swift 解决方案来下载 .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)
    

【讨论】:

【参考方案2】:

更新

他们说来自Docs

Fetch API 提供了一个用于获取资源的接口(包括 通过网络)。用过的人都会觉得很熟悉 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 的桥 (WKScriptMessageHandler) 将其传递给 Swift

使用以下代码将 base64 字符串从 javascript 获取到 Swift。 我正在使用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 的答案中指出的 embed 标记获取源代码

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

var url = document.getElementById("plugin").src

【讨论】:

@Nitesh 很高兴这有效。这能解决您当前的需求吗? 是的。你也可以帮我解决一下 Tarun 的解决方案。或者如果可能的话也更新答案。 @SahilManchanda,这是我不知道的部分,因为它涉及 iOS 代码。感谢您的更新:-) @SahilManchanda 你能帮忙吗?现在突然上面的脚本停止工作了。网站 js 中唯一的变化是 internalinstanceid="4" 之前是 8。 【参考方案3】:

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

你应该在 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 执行中获取返回值是可能的?

【讨论】:

上面的代码不工作或者我不明白如何让它工作。例如,这就是我获取图像的 base64 字符串的方式,它可以帮助您指导我。 var c = document.createElement('canvas'); var ctx = c.getContext('2d'); ctx.drawImage(document.getElementById('captcha_id'), 100, 40); var 值 = c.toDataURL(); value.split(',')[1]; 如何从 swift 代码中获取价值?抱歉我不是IOS开发者 'window.pdf_data = b64;'这个 b64 是字符串吗? 是的,这将包含 base64 字符串。但请记住,这是异步代码。所以该值不会立即可用 我也在用 Sahil 提供的答案尝试这种方法。【参考方案4】:

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

【讨论】:

如果我没记错的话,我们也可以将其存储在 Documents 中。关于直接下载,是的,即使我开始认为这是不可能的。只是想尝试一下,因为我不是 JS 的 gd。 文档,你的意思是在 Mac 上? iPhone 上没有文档应用。您可以尝试使用 Google Drive 应用程序,但这与使用 iBooks 或 iCloud Drive 的过程相同。 可以将pdf直接下载到iPhone文件中。

以上是关于下载 WKWebView 中加载的嵌入式 PDF的主要内容,如果未能解决你的问题,请参考以下文章

请问如何查看pdf内嵌字体?

在 WKWebView swift 中下载文档并加载图像(png、jpeg)、pdf、doc 等

IOS - 如何从 WKWebView 获取缓存的资源?

PDF无法在WKWebView中下载

在 UIWebView/WKWebview 中禁用自动完成

WKWebView 未在 URL 中打开带有梵文字体的 URL