iOS
开发中经常需要和WKWebview
进行打交道,其中最常见的就是和WKWebview
中的H5
页面之间的交互,H5
页面是通过JavaScript
和iOS
进行交互的,本编文章探究下JavaScript
和Swift
之间的交互。
H5页面的嵌入
首先我们新建一个HTML
格式的文件并命名为index.html
其代码如下:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<style>
.switch { position: relative; display: inline-block; width: 60px; height: 34px; }
.switch input { opacity: 0; width: 0; height: 0; }
label.switch { outline: none; }
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; -webkit-transition: .4s; transition: .4s; outline: none; }
.slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: .4s; transition: .4s; }
input:checked + .slider { background-color: #2196F3; }
input:focus + .slider { box-shadow: 0 0 1px #2196F3; }
input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); }
.slider.round { border-radius: 34px; }
.slider.round:before { border-radius: 50%; }
</style>
</head>
<body>
<h2 id="value">Toggle Switch is off</h2>
<label class="switch">
<input type="checkbox" name="myCheckbox">
<span class="slider round"></span>
</label>
</body>
</html>
这个H5
页面的内容非常简单,一个label
和一个toggle switch
,接下来我们在iOS
界面中监听toggle switch
的改变,并同步与H5
进行交互更新label
的值。
WKWebview
利用WKWebview
加载上面创建的HTML
文件:
import UIKit
import WebKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
NSLayoutConstraint.activate([
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
webView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor)
])
if let url = Bundle.main.url(forResource: "index", withExtension: "html") {
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
}
}
private lazy var webView: WKWebView = {
let webView = WKWebView()
webView.translatesAutoresizingMaskIntoConstraints = false
return webView
}()
}
message handel
由于H5
页面是用WKWebview
加载的,WKWebview
又是依托于UIViewController
的,Swift
接受JavaScript
的消息是通过WKScriptMessageHandler
这个协议实现,这个协议提供了一个方法来接受JavaScript
的消息,所以这里我们让UIViewController
遵守WKScriptMessageHandler
协议,并实现一个必要的方法:
extension ViewController: WKScriptMessageHandler{
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let dict = message.body as? [String : AnyObject] else {
return
}
print(dict)
}
}
这里假设JavaScript
发送的消息为Dic
并简单的进行了打印。上面的协议只是让UIViewController
单方面的能接受JavaScript
的消息,内在的WKWebview
是不知道UIViewController
这个角色的存在的,简单的理解是能接受
WKWebview
消息的千万个,我怎么知道是你UIViewController?
为此需要通过WKUserContentController
这个桥梁告知WKWebview
,有一个UIViewController
正在准备接受JavaScript
发出的消息,这里奉上英文解析的WKUserContentController
可能更加贴切:
A WKUserContentController object provides a way for JavaScript to post messages to a web view. The user content controller associated with a web view is specified by its web view configuration.
在上面AutoLayout
的约束后面加上下面这个:
let contentController = self.webView.configuration.userContentController
contentController.add(self, name: "toggleMessageHandler")
JavaScript发送消息:
把下面这段JavaScript
代码放入到HTML
文件的</body>
标签的上面:
<script>
var _selector = document.querySelector('input[name=myCheckbox]');
_selector.addEventListener('change', function(event) {
var message = (_selector.checked) ? "Toggle Switch is on" : "Toggle Switch is off";
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.toggleMessageHandler) {
window.webkit.messageHandlers.toggleMessageHandler.postMessage({
"message": message
});
}
});
</script>
上面的
JavaScript
代码在当toggle switch
打开时向webkit
发送"Toggle Switch is on"
消息,关闭则放送"Toggle Switch is off"
消息。
运行项目后当点击Toggle Switch
后在控制台会有相应的消息输出!
Injecting JavaScript
上面的例子庆幸的拥有
HTML
的源码,并且里面写好了JavaScript
的交互代码,但是实际开发中大多数情况下是没有HTML
源码的,此时如果Swift
想和HTML
页面进行交互可以采取Injecting JavaScript
的形式,简而言之就是Swift
注入一段JavaScript
代码到WKWebview
加载的HTML
页面中来进行二者之间的交互。
注释掉index.html
文件中关于JavaScript
的那段代码,随后在ViewController
中contentController.add(self, name: "toggleMessageHandler")
的下面加上如下代码:
let js = """
var _selector = document.querySelector('input[name=myCheckbox]');
_selector.addEventListener('change', function(event) {
var message = (_selector.checked) ? "Toggle Switch is on" : "Toggle Switch is off";
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.toggleMessageHandler) {
window.webkit.messageHandlers.toggleMessageHandler.postMessage({
"message": message
});
}
});
"""
let script = WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
contentController.addUserScript(script)
当运行项目会发现打印的结果和之前是一样的。
Evaluating JavaScript
通过
Injecting JavaScript
可以让JavaScript
发送消息给Swift
,而通过Evaluating JavaScript
则可以让Swift
发送消息给JavaScript
。
更新userContentController(_: didReceive:)
中的代码如下:
guard let message = dict["message"] else {
return
}
let script = "document.getElementById('value').innerText = \"\(message)\""
webView.evaluateJavaScript(script) { (result, error) in
if let result = result {
print("Label is updated with message: \(result)")
} else if let error = error {
print("An error occurred: \(error)")
}
}
首先拿到JavaScript
发送过来的消息,并利用script
来表示让JavaScript
更新HTML
中label
的值,利用Swift
中WKWebView
的Evaluating JavaScript
来让JavaScript
立马执行这段代码达到更新HTML
文本的目的。
总结
在这篇文章中,我们已经看到了如何实现WKWebView
和它所加载的网页之间的双向交互。利用这一点,我们可以使用网页上的事件来触发应用程序上的动作,同样也可以根据我们在应用程序上的信息来更新加载的网页。