ios - 打开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
}
}
解决方案
到目前为止,您做得非常出色。一个小的调整将修复您的代码。回调需要在与实际回调匹配的方法中
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
这应该是你需要做的所有事情。但是查看导航控制器并了解它们如何工作/为什么以及何时需要它们。它们非常强大,正如您所了解的,它们是必不可少的!
推荐阅读
- neo4j - Neo4j - 从一组特定类型的节点到一个节点的最近邻居
- powershell - 使用 PowerShell 在 Windows 中删除用户配置文件
- vhdl - 如何从 ZTNQ-7020 开发板读取温度?
- angular - NgModel 正在打印我输入的错误值
- bash - 当时间是一个变量时,如何告诉 VLC 在某个时间点开始播放文件?
- karate - 如何断言其中包含特殊字符的字符串?
- java - 切换帧的最佳做法是什么?
- html - 将 HTML 代码嵌入 Google 协作平台时如何删除向下滚动
- pandas - 尝试使用 pd.read_sas() 方法导入 sav 文件失败
- java - java - 如何使用antlr或javaparser查找将字符串文字分配给Java中变量的所有赋值语句?