首页 » 记事本 » WebView与JavaScript通信

WebView与JavaScript通信

说来惭愧,工作多年对客户端与web通信一直停留在一知半解的状态,最近项目中又遇到一些问题,终于有决心去深入挖掘其背后的原理。

由于精力有限,本文所说的APP指IOS APP(Swift),不包括Android APP。

APP如何渲染网页?

苹果提供了UIWebView组件,像浏览器一样可以加载任何网页。iOS8以后,苹果推出了新框架Webkit,提供了替换UIWebView的组件WKWebView。速度更快,占用内存少。

WKWebView

执行JavaScript

//home.html
//...
<h1>Welcome!</h1>
//...
<script>
function redHeader() {
    document.querySelector('h1').style.color = "red";
}
</script>

下面的代码会执行JavaScript的redHeader方法将h1变成红色。

//swift
webView.evaluateJavaScript("redHeader()") { (value, error) in
    print(value ?? "")
}

消息

APP可以通过WKWebView执行JavaScript代码,JavaScript要执行APP的代码又该如何实现?
WKWebView可以添加脚本消息处理程序,使JavaScript可以直接调用并传递消息。

//swift 
let contentController = WKUserContentController()
contentController.add(self,name: "callbackHandler")
let config = WKWebViewConfiguration()
config.userContentController = contentController

JavaScript发送消息:

//javascript
window.webkit.messageHandlers.callbackHandler.postMessage({
    title: "Hello!",
    message: 'Welcome!'
});

APP在接收到消息后弹出:

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    if let body = message.body as? NSDictionary {
        
        let alert = UIAlertController(title: body.object(forKey: "title")! as? String, message: body.object(forKey: "message")! as? String, preferredStyle: UIAlertControllerStyle.alert)
        
        alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.default, handler: nil))
        alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil))
        self.present(alert, animated: true, completion: nil)
        
    }
}

拦截弹出框

通常情况下JavaScript弹出来的弹出框都会带上一个url,如果你的网页经常需要运行在微信端,下面的效果一定会经常见到:

你不得不去模拟一个半透明的浮层和一个弹出框,如果你足够的严谨你甚至会模拟一个细边框(业界统称1像素)。

我是个懒人

通常情况下我只想alert("test!"),还不想让弹出框显示url,该怎么办?APP可以拦截到弹出框并自定义样式和内容。

//swift
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
    let alertController = UIAlertController(title: "提醒", message: message, preferredStyle: .alert)
    alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
        completionHandler()
    }))
    self.present(alertController, animated: true, completion: nil)
}

同理,confirm与prompt也是可以被拦截并自定义样式的。

jsbridge

WKWebView不仅能拦截弹出框,同样能拦截到所有的请求,如果在请求后边加上参数,岂不是同样可以达到通信的目的。

约定一个协议,例如,当链接触发的时候弹出message:

//html
<a href="jsbridge://alert?title=Notice&message=Lauching this missile will destroy the entire universe. Is this what you intended to do?">jsbridge</a>

APP拦截请求弹出:

//swift
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    
    let url = navigationAction.request.url
    let scheme = url?.scheme
    let method = url?.host
    let query = url?.query
    
    if url != nil && scheme == "jsbridge" {
        switch method! {
        case "alert":
            //url.valueOf("test1")
            let alert = UIAlertController(title: url?.valueOf(queryParamaterName: "title"), message: url?.valueOf(queryParamaterName: "message"), preferredStyle: UIAlertControllerStyle.alert)
            
            //add the actions (buttons)
            alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.default, handler: nil))
            alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil))
            
            // show the alert
            self.present(alert, animated: true, completion: nil)
        default:
            print("default")
        }
        decisionHandler(.cancel)
    } else {
        decisionHandler(.allow)
    }
}

利用jsbridge不仅仅能实现弹框,可以实现更多交互,例如分享…

UIWebView

执行JaVascript

下面的代码会获取到标题并输出,接着修改页面标题为红色:

//swift
let title:String = webView.stringByEvaluatingJavaScript(from: "document.title")!
print("title:\(title)" )
//redHeader
webView.stringByEvaluatingJavaScript(from: "redHeader()")

UIWebView不能发消息但是可以拦截请求,与WKWebView通过jsbridge通信方法一致。

源码

https://github.com/thunkli/WebViewJavascript/

此文章发表在 记事本. 将 固定链接 加入收藏.