JS&Swift
加载本地HTML文件
xoverride func loadView() { super.loadView() let conf = WKWebViewConfiguration() //JS调用HTML时使用的name conf.userContentController.add(self, name: "wkbridge") self.wk = WKWebView(frame: CGRect(x: 0, y:20, width: self.view.frame.size.width, height: self.view.frame.size.height - 20), configuration: conf) self.wk.navigationDelegate = self self.wk.uiDelegate = self if let path = Bundle.main.path(forResource: "index", ofType: "html", inDirectory: "www") { let fileUrl = URL(fileURLWithPath: path) self.wk.loadFileURL(fileUrl, allowingReadAccessTo: fileUrl) } //每个页面注入 JS 插件代码 (runPluginJS方法在下面) self.runPluginJS(names: ["Base", "Console", "Sandbox"]) //注入页面间传递的参数 if pageParam != nil { self.wk.evaluateJavaScript("win.pageParam=\(pageParam!)", completionHandler: nil) } self.view.addSubview(self.wk) //添加一个等待指示器 self.view.backgroundColor = UIColor.white activityIndicator = UIActivityIndicatorView() activityIndicator.center = CGPoint(x: self.view.bounds.width/2, y: self.view.bounds.height/2) activityIndicator.color = UIColor.black activityIndicator.startAnimating() self.view.addSubview(activityIndicator)}//注入插件文件func runPluginJS(names: Array<String>) { for name in names { if let path = Bundle.main.path(forResource: name, ofType: "js") { do { let js = try NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) self.wk.evaluateJavaScript(js as String, completionHandler: nil) } catch let error as NSError { print(error.debugDescription) } } }}
在项目根目录中新建 www 文件夹(自定义) 放入 html js css 图片 文件,前端文件都放在www中,方便管理
注意
将wkwebview需要访问的本地文件 添加到项目的 Bundle Resources
HTML 与 Native 交互
ViewController 实现 三个协议 WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler
JS调用Swift代码
js直接使用
xxxxxxxxxx// wkbridge为在WKWebViewConfiguration定定义的name// js传递的param使用json对象 ,比如类似{className: "Console", functionName: "log", data: {msg: "来自js的console"}}window.webkit.messageHandlers.wkbridge.postMessage(param);
会触发swift方法
xxxxxxxxxxfunc userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if message.name == "wkbridge" { if let dic = message.body as? NSDictionary, let className = (dic["className"] as AnyObject).description, let functionName = (dic["functionName"] as AnyObject).description { if let cls = NSClassFromString((Bundle.main.object(forInfoDictionaryKey: "CFBundleName")! as AnyObject).description + "." + className) as? Plugin.Type{ let obj = cls.init() obj.viewController = self obj.wk = self.wk obj.taskId = (dic["taskId"] as AnyObject).integerValue obj.data = (dic["data"] as AnyObject) as? NSDictionary let functionSelector = Selector(functionName) if obj.responds(to: functionSelector) { obj.perform(functionSelector) } else { print("Undefined function :\(functionName)") } } else { print("Class Not Found: \(className)") } } }}
js的方法在 Xcode的控制台打印内容
在swift新建类Console.swift
xxxxxxxxxximport UIKitclass Console: Plugin { func log() { if let string = self.data?["msg"] { print(string) } }}
这里使用到一个基类 Plugin 用来处理 js 与 swift的交互
xxxxxxxxxximport UIKitimport WebKitclass Plugin: NSObject { var viewController: MeiWebView! var wk: WKWebView! var taskId: Int! var data: NSDictionary? required override init() { } func callback(values: NSDictionary) -> Bool { do { let jsonData = try JSONSerialization.data(withJSONObject: values, options: JSONSerialization.WritingOptions()) if let jsonString = NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue) as? String, let tTaskId = self.taskId{ let js = "fireTask(\(tTaskId), '\(jsonString)');" self.wk.evaluateJavaScript(js, completionHandler: nil) return true } } catch let error as NSError{ NSLog(error.debugDescription) return false } return false } func errorCallback(errorMessage: String) { let js = "onError(\(self.taskId), '\(errorMessage)');" self.wk.evaluateJavaScript(js, completionHandler: nil) } func convertToDictionary(text: String) -> [String: Any]? { if let data = text.data(using: .utf8) { do { print(text) print(data) return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] } catch { print("JSON 转换失败 :" + error.localizedDescription) } } return nil } }
这样js的方法就会在 Xcode的控制台打印出 内容了
对于隐藏接口,给前段一个友好的方式,使用自定义JS文件,使用上面的runJSPlugin直接写入到html中,在runJSPlugin 方法中 用到的js文件, 在项目中新建一个GROUP 统一管理,我觉得与www文件夹中的前端文件分离比较好。方法使用数组传递需要写入的JS的名称,这里列出Console.js
xxxxxxxxxxvar console = { log: function(message) { //console.log(msg); window.webkit.messageHandlers.wkbridge.postMessage({className: "Console", functionName: "log", data: {msg: message}}); }};
这样在js中只要执行 console.log() 就可以在Xcode控制台打印内容了。
Swift接口回调
js 层使用队列管理, 新建Base.js
xxxxxxxxxxQueue = [];function Task(id, callback, errorCallback) { var mTask = new Object; mTask.id = id; mTask.callback = callback; mTask.errorCallback = errorCallback; mTask.once = false; return mTask;}fireTask = function(i, j) { if (typeof Queue[i].callback == 'function') { Queue[i].callback(JSON.parse(j)); if (Queue[i].once) Queue[i] = null; } };onError = function (i, j) { Queue[i].errorCallback(j);};
实现获取沙盒根目录
Sandbox.swift
ximport UIKitclass Sandbox: Plugin { func getRootPath() { let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let dic = NSDictionary.init(object: path.absoluteString, forKey: "rootPath" as NSCopying) _ = callback(values: dic) }}
新建 Sandbox.js
xxxxxxxxxxSandbox = { getRootPath: function(onSuccess, onError) { Queue.push(Task(Queue.length, onSuccess, onError)); window.webkit.messageHandlers.wkbridge.postMessage({className: "Finder", functionName: "getRootPath", taskId: Queue.length - 1}); },};
js中执行
xxxxxxxxxxSandbox.getRootPath(function(path){ console.log(path.rootPath); //获取沙盒路径})
Swift调用JS代码
xxxxxxxxxxlet js = "jsFunction(param);"self.wk.evaluateJavaScript(js, completionHandler: nil)
访问沙盒文件
将文件转换成base64的字符串传递给js, src直接设置data。
WKWebView 只能读取tmp中的文件,所以将文件写入到 NSTemporaryDirectory() 的路径下面就可以了,以后js需要访问什么文件,将文件复制到tmp文件夹就可以访问了,按需删除就好了。
推荐文章
祈
你的努力没人会看到,可成功会让人羡慕。 2020.12.08 加入
iOS交流群:642363427 公众号:iOS进阶宝典 视频学习:https://space.bilibili.com/107521719
评论