首页 > 解决方案 > 打开pdf,ppt ..等文件时,如何使用swift语言在Xcode中添加关闭或后退按钮?

问题描述

首先,我是 swift 和客观的 c 语言的新手。我的问题是我有一个使用 swift 语言的 iOS 设备 WebView 应用程序,该应用程序在打开 pdf、ppt 等文档文件时使用。没有关闭文件或返回上一个选项的选项。刚刚搜索了这个问题,并在下面的链接中找到了目标 c 的解决方案,但我的问题是我使用的是 swift 而不是目标 c。
xCode在打开pdf文件时添加关闭/完成按钮 ,我的代码是:

import UIKit
import WebKit
import QuickLook
import AVFoundation

类视图控制器: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
    }

    
    
}

标签: iosswiftxcodewebviewwebviewdidfinishload

解决方案


到目前为止,您做得非常出色。一个小的调整将修复您的代码。回调需要在与实际回调匹配的方法中

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

移动canGoBack代码如下:

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

它调用的方法可能如下所示(基于您提供的链接):

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

编辑:(这是将控制器嵌入 UINavigationController 的方法)

我假设您正在使用 Interface Builder。(这不是我会这样做的方式,但它应该适合你)。

单击 LaunchScreen.storyboard(在左侧栏中)

单击并展开 View Controller Scene(顶部右侧的下一列)

单击视图控制器

移动到屏幕顶部并从菜单中选择:

/Editor/Embed In/Navigation Controller

开始

扩张

嵌入

这应该是你需要做的所有事情。但是查看导航控制器并了解它们如何工作/为什么以及何时需要它们。它们非常强大,正如您所了解的,它们是必不可少的!


推荐阅读