6

As I know, caching for the all resources loaded in WKWebView is set by default. There are several post about How to remove cached resources from WKWebView? but I can't find any posts about the way to get it from WKWebView.

For example, when I use WKWebView to load this url, it displays a pdf file and what I want is getting this pdf file from WKWebView to share after url is loaded completely

I have checked on Chrome, if I want to share the file from above link, it will request and download content from url again after that displaying share dialog. It means that they don't get it from current WKWebView (cached resources).

But on Safari, somehow they show share dialog immediately after clicking on share button and seem like without downloading again. I think they get pdf file from cache or somewhere in WkWebView and share it.

Another way to understand the question is How to get a file which is presented on WKWebView without downloading content again?

enter image description here

If the question doesn't have enough information for an answer, feel free to leave a comment and I will make it clearer.

trungduc
  • 11,926
  • 4
  • 31
  • 55
  • Have you found any solution to this? – Mrug Aug 14 '18 at 14:35
  • @Mrug Marked answer provides a possible solution, you can take a look at it. – trungduc Aug 14 '18 at 15:32
  • I tried that but it has an issue with PDF which has URL with some security-related headers. https://stackoverflow.com/questions/51871789/get-loaded-pdf-directly-from-wkwebview – Mrug Aug 16 '18 at 10:00

1 Answers1

4

As I know, caching for the all resources loaded in WKWebView is set by default: True

By keeping that in mind if you request for the same resource, webview will not load content from internet it will give you the content from the cached resources. For requesting the same resource, you can use some JavaScript to get the content.

Look at the below code. Once the PDF is loaded and you hit the save button. it will execute JavaScript Code and when the data is ready to deliver by JavaScript

It will fire window.webkit.messageHandlers.myInterface.postMessage(base64)

to let your ViewController know that data is ready to be shared.

You can verify the same by

  1. Let it load the PDF

  2. Turn off the simulator's internet(see)

  3. Hit the save button

You will get your pdf data in base64 format. save it and share it :)

    import UIKit
    import WebKit
    class ViewController: UIViewController {

        @IBOutlet weak var webView: WKWebView!
        var activityIndicator: UIActivityIndicatorView?
        override func viewDidLoad() {
            super.viewDidLoad()

        }

        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
            webView.navigationDelegate = self
            activityIndicator?.center = self.view.center
            self.view.addSubview(activityIndicator!)
            webView.configuration.userContentController.add(self, name: "myInterface")
            webView.load(URLRequest(url: URL(string: "http://www.africau.edu/images/default/sample.pdf")!))
            activityIndicator?.startAnimating()
        }

        @IBAction func saveAction(_ sender: Any) {
            let s = """
            var xhr = new XMLHttpRequest();
            xhr.open('GET', "\(webView.url?.absoluteString ?? "")", true);
            xhr.responseType = 'arraybuffer';
            xhr.onload = function(e) {
            if (this.status == 200) {
            var uInt8Array = new Uint8Array(this.response);
            var i = uInt8Array.length;
            var binaryString = new Array(i);
            while (i--){
            binaryString[i] = String.fromCharCode(uInt8Array[i]);
            }
            var data = binaryString.join('');
            var base64 = window.btoa(data);

       window.webkit.messageHandlers.myInterface.postMessage(base64);
            }
            };
            xhr.send();
            """




            webView?.evaluateJavaScript(s, completionHandler: {(string,error) in
                print(error ?? "no error")
            })
        }
    }

    extension ViewController: WKScriptMessageHandler{
      func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    //    print("Message received: \(message.name) with body: \(message.body)")

        guard
          var documentsURL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last,
          let convertedData = Data.init(base64Encoded: message.body as! String)
          else {
            //handle error when getting documents URL
            return
        }

        //name your file however you prefer
        documentsURL.appendPathComponent("sample.pdf")

        do {
          try convertedData.write(to: documentsURL)
        } catch {
          //handle write error here
        }

        //if you want to get a quick output of where your
        //file was saved from the simulator on your machine
        //just print the documentsURL and go there in Finder
        print(documentsURL)

        let activityViewController = UIActivityViewController.init(activityItems: [documentsURL], applicationActivities: nil)
        present(activityViewController, animated: true, completion: nil)
      }
    }

    extension ViewController: WKNavigationDelegate{
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            self.activityIndicator?.stopAnimating()
            self.activityIndicator?.removeFromSuperview()
            self.activityIndicator = nil
        }
    }

BTW the pdf link you provided is using HTTP not HTTPS. So for testing purpose add the following in your info.plist

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoadsInWebContent</key>
        <true/>
    </dict>
trungduc
  • 11,926
  • 4
  • 31
  • 55
Sahil Manchanda
  • 9,812
  • 4
  • 39
  • 89
  • 1
    Great!!! Seem like it's what I'm expected. I will check your code and let you know as soon as possible. Thank you very much. – trungduc Jun 17 '18 at 11:00
  • @trungduc nice addition to the answer – Sahil Manchanda Jun 18 '18 at 08:41
  • Injection is not working for all cases. `Blocked script execution in 'URL' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.` – Mrug Aug 16 '18 at 07:02
  • @Mrug are you using IFRAME ? – Sahil Manchanda Aug 16 '18 at 07:04
  • Nope. The iframe is not there, but the previous page from where PDF was loaded may have iframe. – Mrug Aug 16 '18 at 07:17
  • @Mrug Please ask a new Question on SO and provide some details like url and how to reproduce the error. I will try to help my best – Sahil Manchanda Aug 16 '18 at 07:22
  • just posted https://stackoverflow.com/questions/51871789/get-loaded-pdf-directly-from-wkwebview Thanks – Mrug Aug 16 '18 at 07:29
  • @SahilManchanda I don't think this answer works in iOS 13.4.1. I get the error: `Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=Cannot execute JavaScript in this document, WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=A JavaScript exception occurred}` – alobaili May 12 '20 at 11:26
  • @alobaili apologies for late replying. can you please ask a new question on SO and put some code and explanation that what you are trying to achieve and what went wrong. so that i can help – Sahil Manchanda May 15 '20 at 18:57
  • I'm trying to achieve the same exact result asked in this question. I have a `WKWebView` fetching a PDF from the web then, when the user taps the share button, I'm trying to access the same PDF from the cached resources without having to download it again and present an activity view controller for it. I think your solution worked before, but not in iOS 13.4.1. Somehow they changed it so that JavaScript cannot be executed on a web view that only displays a PDF document. I have copied the same code you are using here. Maybe it should be updated to work in iOS 13.4.1 – alobaili May 16 '20 at 19:58