- 加載本地HTML文件
x
override 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方法
xxxxxxxxxx
func 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
xxxxxxxxxx
import UIKit
class Console: Plugin {
func log() {
if let string = self.data?["msg"] {
print(string)
}
}
}
這里使用到一個基類 Plugin 用來處理 js 與 swift的交互
xxxxxxxxxx
import UIKit
import WebKit
class 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
xxxxxxxxxx
var 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
xxxxxxxxxx
Queue = [];
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
x
import UIKit
class 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
xxxxxxxxxx
Sandbox = {
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中執行
xxxxxxxxxx
Sandbox.getRootPath(function(path){
console.log(path.rootPath); //獲取沙盒路徑
})
- Swift調用JS代碼
xxxxxxxxxx
let js = "jsFunction(param);"
self.wk.evaluateJavaScript(js, completionHandler: nil)
- 訪問沙盒文件
- 將文件轉換成base64的字符串傳遞給js, src直接設置data。
- WKWebView 只能讀取tmp中的文件,所以將文件寫入到 NSTemporaryDirectory() 的路徑下面就可以了,以后js需要訪問什么文件,將文件復制到tmp文件夾就可以訪問了,按需刪除就好了。