First of all I'm new in swift and objective c languages. My problem is that I have a WebView app for iOS device using swift language , the app when opens a document file such as pdf, ppt,...etc. there is no option to close the file or back to previous. Just googled the problem and found the solution with objective c in the link below, but my problem is that I'm using swift not objective c.
xCode add close/done button when pdf file is opened and my code is:

import UIKit
import WebKit
import QuickLook
import AVFoundation

class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, QLPreviewControllerDataSource, WKScriptMessageHandler {

  var documentPreviewController = QLPreviewController()
  var documentUrl = URL(fileURLWithPath: "")
  
  let webViewConfiguration = WKWebViewConfiguration()
  let userContentController = WKUserContentController()
  var webViewCookieStore: WKHTTPCookieStore!


var webView: WKWebView!

override func loadView() {
    let webConfiguration = WKWebViewConfiguration()
    webView = WKWebView(frame: .zero, configuration: webConfiguration)
    webView.uiDelegate = self
    view = webView
}
override func viewDidLoad() {
    super.viewDidLoad()
    
    // initial configuration of custom JavaScripts
     webViewConfiguration.userContentController = userContentController
     webViewConfiguration.websiteDataStore = WKWebsiteDataStore.default()
     
     
     // init this view controller to receive JavaScript callbacks
    userContentController.add(self, name: "openDocument")
     userContentController.add(self, name: "jsError")
     
     
     // QuickLook document preview
    documentPreviewController.dataSource  = self
     
     // link the appDelegate to be able to receive the deviceToken
    
    //------------
           // Add script message handlers that, when run, will make the function
           // window.webkit.messageHandlers.test.postMessage() available in all frames.
           // controller.add(self, name: "test")
           guard let scriptPath = Bundle.main.path(forResource: "script", ofType: "js"),
               let scriptSource1 = try? String(contentsOfFile: scriptPath) else { return }
           
           let userScript = WKUserScript(source: scriptSource1, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
           userContentController.addUserScript(userScript)
           //----- end
           
    
    
     webView = WKWebView(frame: CGRect.zero, configuration: webViewConfiguration)
     
    
    
    
    let myURL = URL(string:"My URL")
    let myRequest = URLRequest(url: myURL!)
    // see "The 2 delegates": // https://samwize.com/2016/06/08/complete-guide-to-implementing-wkwebview/
           webView.uiDelegate = self
          webView.navigationDelegate = self
           view.addSubview(webView)
           let layoutGuide = view.safeAreaLayoutGuide
           webView.translatesAutoresizingMaskIntoConstraints = false
           webView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
           webView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
           webView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
           webView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
           
    self.load(myRequest)
    webViewCookieStore = webView.configuration.websiteDataStore.httpCookieStore
          
          

          webView.allowsBackForwardNavigationGestures = true
         
          if(webView.canGoBack) {
              //Go back in webview history
              webView.goBack()
          } else {
              //Pop view controller to preview view controller
              self.navigationController?.popViewController(animated: true)
          }
          
          
          let cameraMediaType = AVMediaType.video
          let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: cameraMediaType)
          
          switch cameraAuthorizationStatus {
          case .denied: break
          case .authorized: break
          case .restricted: break
            
              
          case .notDetermined:
              // Prompting user for the permission to use the camera.
              AVCaptureDevice.requestAccess(for: cameraMediaType) { granted in
                  if granted {
                      print("Granted access to \(cameraMediaType)")
                  } else {
                      print("Denied access to \(cameraMediaType)")
                  }
              }
            @unknown default:
            fatalError()
          }
    
}

   private func load(_ url: URL) {
       load(URLRequest(url:url))
   }
   private func load(_ req: URLRequest) {
    let request = req
      // request.setValue(self.deviceToken, forHTTPHeaderField: "iosDeviceToken")
       //request.setValue(self.myVersion as? String, forHTTPHeaderField: "iosVersion")
       //request.setValue(self.myBuild as? String, forHTTPHeaderField: "iosBuild")
       //request.setValue(UIDevice.current.modelName, forHTTPHeaderField: "iosModelName")
       //debugPrintHeaderFields(of: request, withMessage: "Loading request")
       webView.load(request)
       debugPrint("Loaded request=\(request.url?.absoluteString ?? "n/a")")
   }
   func webView(_ webView: WKWebView,
                decidePolicyFor navigationAction: WKNavigationAction,
                decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
       let url = navigationAction.request.url
       
       
       if openInDocumentPreview(url!) {
        decisionHandler(.cancel)
           executeDocumentDownloadScript(forAbsoluteUrl: url!.absoluteString)
       } else {
           decisionHandler(.allow)
       }
   }
   
   /*
    Handler method for JavaScript calls.
    Receive JavaScript message with downloaded document
    */
   public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
       debugPrint("did receive message \(message.name)")
       
       
       if (message.name == "openDocument") {
           previewDocument(messageBody: message.body as! String)
       } else if (message.name == "jsError") {
           debugPrint(message.body as! String)
       }
   }
   
   /*
    Open downloaded document in QuickLook preview
    */
   private func previewDocument(messageBody: String) {
       // messageBody is in the format ;data:;base64,
       
       // split on the first ";", to reveal the filename
       let filenameSplits = messageBody.split(separator: ";", maxSplits: 1, omittingEmptySubsequences: false)
       
       let filename = String(filenameSplits[0])
       
       // split the remaining part on the first ",", to reveal the base64 data
       let dataSplits = filenameSplits[1].split(separator: ",", maxSplits: 1, omittingEmptySubsequences: false)
       
       let data = Data(base64Encoded: String(dataSplits[1]))
       
       if (data == nil) {
           debugPrint("Could not construct data from base64")
           return
       }
       
       // store the file on disk (.removingPercentEncoding removes possible URL encoded characters like "%20" for blank)
       let localFileURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename.removingPercentEncoding ?? filename)
       
       do {
           try data!.write(to: localFileURL);
       } catch {
           debugPrint(error)
           return
       }
       
       // and display it in QL
       DispatchQueue.main.async {
           self.documentUrl = localFileURL
           self.documentPreviewController.refreshCurrentPreviewItem()
           self.present(self.documentPreviewController, animated: true, completion: nil)
       }
   }
   
   /*
    Implementation for QLPreviewControllerDataSource
    */
   func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
       return documentUrl as QLPreviewItem
   }
   
   
   /*
    Implementation for QLPreviewControllerDataSource
    We always have just one preview item
    */
   func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
       return 1
   }
   
   
   /*
    Checks if the given url points to a document download url
    */
   private func openInDocumentPreview(_ url : URL) -> Bool {
       // this is specific for our application - can be everything in your application
       return url.absoluteString.contains("/APP/connector")
   }
   
   /*
    Intercept the download of documents in webView, trigger the download in JavaScript and pass the binary file to JavaScript handler in Swift code
    */
   private func executeDocumentDownloadScript(forAbsoluteUrl absoluteUrl : String) {
       // TODO: Add more supported mime-types for missing content-disposition headers
       webView.evaluateJavaScript("""
           (async function download() {
           const url = '\(absoluteUrl)';
           try {
           // we use a second try block here to have more detailed error information
           // because of the nature of JS the outer try-catch doesn't know anything where the error happended
           let res;
           try {
           res = await fetch(url, {
           credentials: 'include'
           });
           } catch (err) {
           window.webkit.messageHandlers.jsError.postMessage(`fetch threw, error: ${err}, url: ${url}`);
           return;
           }
           if (!res.ok) {
           window.webkit.messageHandlers.jsError.postMessage(`Response status was not ok, status: ${res.status}, url: ${url}`);
           return;
           }
           const contentDisp = res.headers.get('content-disposition');
           if (contentDisp) {
           const match = contentDisp.match(/(^;|)\\s*filename=\\s*(\"([^\"]*)\"|([^;\\s]*))\\s*(;|$)/i);
           if (match) {
           filename = match[3] || match[4];
           } else {
           // TODO: we could here guess the filename from the mime-type (e.g. unnamed.pdf for pdfs, or unnamed.tiff for tiffs)
           window.webkit.messageHandlers.jsError.postMessage(`content-disposition header could not be matched against regex, content-disposition: ${contentDisp} url: ${url}`);
           }
           } else {
           window.webkit.messageHandlers.jsError.postMessage(`content-disposition header missing, url: ${url}`);
           return;
           }
           if (!filename) {
           const contentType = res.headers.get('content-type');
           if (contentType) {
           if (contentType.indexOf('application/json') === 0) {
           filename = 'unnamed.pdf';
           } else if (contentType.indexOf('image/tiff') === 0) {
           filename = 'unnamed.tiff';
           }
           }
           }
           if (!filename) {
           window.webkit.messageHandlers.jsError.postMessage(`Could not determine filename from content-disposition nor content-type, content-dispositon: ${contentDispositon}, content-type: ${contentType}, url: ${url}`);
           }
           let data;
           try {
           data = await res.blob();
           } catch (err) {
           window.webkit.messageHandlers.jsError.postMessage(`res.blob() threw, error: ${err}, url: ${url}`);
           return;
           }
           const fr = new FileReader();
           fr.onload = () => {
           window.webkit.messageHandlers.openDocument.postMessage(`${filename};${fr.result}`)
           };
           fr.addEventListener('error', (err) => {
           window.webkit.messageHandlers.jsError.postMessage(`FileReader threw, error: ${err}`)
           })
           fr.readAsDataURL(data);
           } catch (err) {
           // TODO: better log the error, currently only TypeError: Type error
           window.webkit.messageHandlers.jsError.postMessage(`JSError while downloading document, url: ${url}, err: ${err}`)
           }
           })();
           // null is needed here as this eval returns the last statement and we can't return a promise
           null;
       """) { (result, err) in
           if (err != nil) {
               debugPrint("JS ERR: \(String(describing: err))")
           }
       }
   }


    var toolbars: [UIView] = []
    
    var observations : [NSKeyValueObservation] = []
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        navigationController?.toolbar.isHidden = false
        
        if let navigationToobar = navigationController?.toolbar {
            let observation = navigationToobar.observe(\.isHidden) {[weak self] (changedToolBar, change) in
                
                if self?.navigationController?.toolbar.isHidden == true {
                    self?.navigationController?.toolbar.isHidden = false
                }
            }
            observations.append(observation)
        }
        
        toolbars = toolbarsInSubviews(forView: view)
        
        for toolbar in toolbars {
            
            toolbar.isHidden = false
            
            let observation = toolbar.observe(\.isHidden) { (changedToolBar, change) in
                if let isHidden = change.newValue,
                    isHidden == true {
                    changedToolBar.isHidden = false
                }
            }
            
            observations.append(observation)
        }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(false)
        //This hides the share item
        if let add =  self.children.first as? UINavigationController {
            if let layoutContainerView  = add.view.subviews[1] as? UINavigationBar {
                layoutContainerView.subviews[2].subviews[1].isHidden = false
            }
        }
        
        
      
        
    }
    private func toolbarsInSubviews(forView view: UIView) -> [UIView] {
        
        var toolbars: [UIView] = []
        
        for subview in view.subviews {
            if subview is UIToolbar {
                toolbars.append(subview)
            }
            toolbars.append(contentsOf: toolbarsInSubviews(forView: subview))
        }
        return toolbars
    }

    
    
}
1

There are 1 best solutions below

3
On

You've done an excellent job getting this far. A small adjustment will fix your code. The callback needs to be inside a method matching the actual callback

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)

Move the canGoBack code as follows:

  func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        if webView.canGoBack {
            let backButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(backButtonPressed))
            navigationItem.rightBarButtonItem = backButton
        } else {
            self.navigationItem.rightBarButtonItem = nil
        }
    }

And the method it calls can look like this (based on the link you provided):

  @objc
    func backButtonPressed() {
        if webView.canGoBack {
            webView.goBack()
        } else {
            self.navigationController?.popViewController(animated: true)
        }
    }

Edit: (this is how to embed your controller inside a UINavigationController)

I've made an assumption that you're using Interface Builder. (This isn't how I would do this, but it should work for you).

Click on LaunchScreen.storyboard (in the left side column)

Click on and expand the View Controller Scene (the next column to the right at the top)

Click on the ViewController

Move to the top of the screen and select from the menu:

/Editor/Embed In/Navigation Controller

Start

Expand

Embed

That should be all you need to do. But look up navigation controllers and learn how they work/why and when you need them. They're very powerful, and as you've learned, they're essential!