在WKWebView中下载嵌入的PDF文件

8

当从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字符串或其他下载方法?


你能在Webview中执行JavaScript代码吗? - Tarun Lalwani
@TarunLalwani 是的。 - Nitesh
你能解释得更详细一些吗?或者提供一张你正在尝试加载的网页图片吗? - tara tandel
@Nitesh,请看一下我的回答。 - Sahil Manchanda
4个回答

5

这个问题之前有人问过,但是如果有人正在寻找在File Manager上下载.pdf或任何文件的解决方案,并且希望使用WKWebView,那么这就是我得出的结论。

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

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

只需添加以下函数并进行检查。

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

3

更新

根据文档,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和XMLHttpRequest都是异步的..你只需要等待处理完成后,通过javascript的桥接器(WKScriptMessageHandler)将其传递给Swift

使用以下代码从javascript获取base64字符串到Swift中。我正在使用WKScriptMessageHandler来获取来自Javascript的回调当base64字符串准备好被消耗时。在字符串s中,您只需要传递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的开头添加下面,并将url传递到xhr.open中。
var url = document.getElementById("plugin").src

@Nitesh,很高兴这个方法有效。这解决了你目前的需求吗? - Sahil Manchanda
是的。同时,您能否帮我更多地了解Tarun的解决方案呢?或者如果可能的话,也可以更新答案。 - Nitesh
@SahilManchanda,这是我不知道的部分,因为它涉及到iOS代码。感谢您的更新 :-) - Tarun Lalwani
@SahilManchanda 你能帮忙吗?现在突然以上脚本停止运行了,网站的js中唯一改变的是internalinstanceid="4"之前是8。 - Nitesh
<body style="height: 100%; width: 100%; overflow: hidden; margin: 0px; background-color: rgb(82, 86, 89);"><embed width="100%" height="100%" name="plugin" id="plugin" src="" type="application/pdf" internalinstanceid="4"></body> - Nitesh
显示剩余3条评论

2

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


以上代码无法运行,或者我不理解如何使其运行。举个例子,这是我获取图像的base64字符串的方法,如果有帮助,请指导我。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
你怎样从 Swift 代码中获取数值?抱歉,我不是 IOS 开发人员。 - Tarun Lalwani
'window.pdf_data = b64;' 这里的b64是字符串吗? - Nitesh
是的,这将包含Base64字符串。但请记住,这是异步代码。因此,该值不会立即可用。 - Tarun Lalwani
我也在尝试使用Sahil提供的答案来尝试这种方法。 - Nitesh

-2

您想将PDF下载到iPhone还是Mac上?通常情况下,直接在iPhone上下载PDF是不可能的,因为iPhone本身没有存储PDF的能力。您需要拥有iBooks或iCloud Drive,然后在另一个窗口中打开PDF,再手动下载。在下载PDF之前仍需要用户交互,也就是说用户必须批准下载。通过将JavaScript注入WKWebView实例进行直接下载是不可能的。


1
如果我没记错的话,我们也可以将它存储在“文档”中。关于直接下载,是的,我也开始觉得这不可能了。只是想试一试,因为我不太擅长JS。 - Nitesh
文档,您是指在Mac上吗? - ch1maera
iPhone上没有文档应用程序。您可以尝试使用Google Drive应用程序,但这与使用iBooks或iCloud Drive的过程相同。 - ch1maera
1
可以直接将PDF下载到iPhone文件中。 - Abin Baby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接