主要内容
- Safari调试
- swift/OC与JS互调
- 增加加载进度条
- 支持JS中alert、confirm、prompt
Safari调试
设置 —> safari --> 高级,开启JavaScript、网页检查器
打开Safari浏览器,选择调试的网页,同样在js里面可以断点调试:
swift/OC与JS互调
这里只介绍Swift,OC可以照着swift翻译即可
- swift调用JS,并传参数
先定义一个JS函数,如:
function add(params) {
let msg = JSON.stringify(params);
showMsg("调用了add函数")
}
swift调用JS实际上就是调用WKWebView的evaluateJavaScript方法
/* @abstract Evaluates the given JavaScript string.
@param javaScriptString The JavaScript string to evaluate.
@param completionHandler A block to invoke when script evaluation completes or fails.
@discussion The completionHandler is passed the result of the script evaluation or an error.
Calling this method is equivalent to calling `evaluateJavaScript:inFrame:inContentWorld:completionHandler:` with:
- A `frame` value of `nil` to represent the main frame
- A `contentWorld` value of `WKContentWorld.pageWorld`
*/
open func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil)
参数javaScriptString就是JS的函数名+参数,如:
调用add的JS函数,javaScriptString为"add({\n "name" : "jack"\n})"
可以简单封装下调用方法:
/// 调用JS函数
/// - Parameters:
/// - jsFunName: JS函数名
/// - params: 传给JS函数的参数
/// - Returns:
func callJSFunction(jsFunName: String, params: Any?) -> Void {
var js = String(format: "%@()", jsFunName)
if let valueStr = params as? String {
js = String(format: "%@('%@')", jsFunName, valueStr)
} else if let valueNum = params as? NSNumber {
js = String(format: "%@(%@)", jsFunName, valueNum)
} else if let valueBool = params as? Bool {
if valueBool {
js = jsFunName + "(true)"
} else {
js = jsFunName + "(false)"
}
} else if let valueDiction = params {
if let temValue = converObjToJson(obj: valueDiction) {
js = String(format: "%@(%@)", jsFunName, temValue)
} else {
js = String(format: "%@()", jsFunName)
}
} else {
js = String(format: "%@()", jsFunName)
}
self.webView?.evaluateJavaScript(js) { (item, error) in
}
}
- JS调用swift,并传参数
在JS端调用window.webkit.messageHandlers.自定义一个名称.postMessage(params),如:
var params = {"name": "JSName"};
window.webkit.messageHandlers.minus.postMessage(params);
这个自定义的名称需要提前设置到WKWebView上,设置方法:
/** @abstract Adds a script message handler to the main world used by page content itself.
@param scriptMessageHandler The script message handler to add.
@param name The name of the message handler.
@discussion Calling this method is equivalent to calling addScriptMessageHandler:contentWorld:name:
with [WKContentWorld pageWorld] as the contentWorld argument.
*/
open func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String)
如:
let userContent = WKUserContentController()
// 配置JS可以调用的名称
let jsCallFun: [String] = ["minus"]
for value in jsCallFun {
userContent.add(self, name: value)
}
let config = WKWebViewConfiguration()
config.preferences = preferences
config.userContentController = userContent
config.suppressesIncrementalRendering = true
配置完后,当JS调用window.webkit.messageHandlers.minus.postMessage(params);时会进入到WSWebView的WKScriptMessageHandler代理方法userContentController
如:
// MARK: - WKScriptMessageHandler
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
let name = message.name // 注册过的JS可调用的名称
if name.elementsEqual("minus") {
if let params = message.body as? NSDictionary { // JS传递的参数
if let name = params.value(forKey: "name") as? String {
minus(name: name)
}
}
}
}
func minus(name: String) -> Void {
showAlert(title: "Swift minus方法", message: "name:\(name)", modelArray: nil, cancelTitle: "确定", cancelHandle: {
}, vc: self, sourceView: webView)
}
总结:
我们常用的需求更多的场景应该是JS调用swift的某个方法后,再回调JS的某个方法,达到异步回调的效果,这时我们可以这样设计:
把JS的回调函数名当作参数传给swift的方法,swift处理完逻辑后直接调用这个参数:
var params = {"name": "JSName","callBackFun": "minusCallback"};
window.webkit.messageHandlers.minus.postMessage(params);
而swift端:
// MARK: - WKScriptMessageHandler
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
let name = message.name // 注册过的JS可调用的名称
if name.elementsEqual("minus") {
if let params = message.body as? NSDictionary { // JS传递的参数
if let name = params.value(forKey: "name") as? String {
let callback = params.value(forKey: "callBackFun") as? String
minus(name: name, callBack: callback)
}
}
}
}
func minus(name: String, callBack: String?) -> Void {
showAlert(title: "Swift minus方法", message: "name:\(name)", modelArray: nil, cancelTitle: "确定", cancelHandle: { [weak self] in
if let callBackFun = callBack {
guard let self = self else { return }
self.callJSFunction(jsFunName: callBackFun, params: nil)
}
}, vc: self, sourceView: webView)
}
swift调用JS同理!
增加加载进度条
进度条使用第三方库NJKWebViewProgress
pod 'NJKWebViewProgress'
在WKWebView上面预留2个高度显示进度条,使用ReactiveCocoa监听WKWebView的进度:
self.webView?.reactive.signal(forKeyPath: "estimatedProgress").observeValues({ [weak self](result) in
guard let self = self else {return}
if let progress = result as? NSNumber {
self.progressView?.setProgress(progress.floatValue, animated: true)
if progress.floatValue >= 1.0 {
self.progressWrapViewHeightConst.constant = 0
}
}
})
其他地方涉及到进度的地方再优化下:
// MARK: - WKNavigationDelegate
// MARK: 页面开始加载时调用
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.progressWrapViewHeightConst.constant = self.progressHeight
}
// MARK: 内容开始返回时调用
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
self.progressWrapViewHeightConst.constant = 0
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
self.progressWrapViewHeightConst.constant = 0
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.progressWrapViewHeightConst.constant = 0
}
支持JS中alert、confirm、prompt
在JS端调用alert、confirm、prompt这几个方法,实际上会回调到WKWebView的WKUIDelegate代理中,对应的方法为:
@available(iOS 8.0, *)
optional func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void)
@available(iOS 8.0, *)
optional func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void)
@available(iOS 8.0, *)
optional func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void)
需要实现具体方法才能有效果:
// MARK: - WKUIDelegate
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
showAlert(title: "温馨提示", message: message, modelArray: nil, cancelTitle: "确定", cancelHandle: {
completionHandler()
}, vc: self, sourceView: webView)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
var modelArray: [AlertModel] = [AlertModel]()
let model = AlertModel(title: "确定") {
completionHandler(true)
}
modelArray.append(model)
showAlert(title: "温馨提示", message: message, modelArray: modelArray, cancelTitle: "取消", cancelHandle: {
completionHandler(false)
}, vc: self, sourceView: webView)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
let alertController = UIAlertController(title: prompt, message: defaultText, preferredStyle: UIAlertController.Style.alert)
alertController.addTextField { (txtField) in
txtField.text = defaultText
}
alertController.addAction(UIAlertAction(title: "确定", style: UIAlertAction.Style.default, handler: { (alertAction) in
let txt = alertController.textFields?[0].text
completionHandler(txt)
}))
self.present(alertController, animated: true) {
}
}
源码下载