一、WKWebView的使用:
1、初始化
webView初始化:WKPreferences, WKUserContentController -> WKWebViewConfiguration -> WKWebView
这里添加了三个代理,代理方法会在下面实现。
代码语言:javascript复制let preferences = WKPreferences()
preferences.javaScriptEnabled = true
preferences.javaScriptCanOpenWindowsAutomatically = true // default value is NO in iOS and YES in OS X.
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences;
configuration.userContentController = WKUserContentController()
// 注册JS发送消息的name (自定义,可以设置多个)
configuration.userContentController.add(self, name: "moxiaoyan") // 添加 WKScriptMessageHandler 代理
let webView = WKWebView(frame: CGRect(x: 0, y: (navigationController?.navigationBar.frame.maxY ?? 0) 1, width: view.frame.size.width, height: view.frame.size.height/2), configuration: configuration)
webView.navigationDelegate = self // 添加 WKNavigationDelegate 代理
webView.uiDelegate = self // 添加 WKUIDelegate 代理
进度条:显示页面加载进度,加载完成后 hidden (不需要的可以跳过)
代码语言:javascript复制// 加载进度条
progressView = UIProgressView(progressViewStyle: .default)
progressView.progressTintColor = .green
progressView.trackTintColor = .lightGray
progressView.progress = 0.0
progressView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 1)
webView.addSubview(progressView)
// 添加观察者
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress" {
progressView.setProgress(Float(webView.estimatedProgress), animated: true)
if webView.estimatedProgress >= 1.0 {
progressView.isHidden = true
}
}
}
向前<、向后>、关闭X 按钮实现 (不需要的可以跳过)
当`webView`可以`goForward`时显示`向前`按钮;
当`webView`可以`goBack`时显示`向后`按钮;
代码语言:javascript复制lazy var forwardBtnItem: UIBarButtonItem = {
let btn = UIBarButtonItem(image: UIImage(named: "mo_green_forward"), style: .done, target: self, action: #selector(goForward))
btn.imageInsets = UIEdgeInsets(top: 0, left: -30, bottom: 0, right: 30)
return btn
}()
private lazy var closeBtnItem: UIBarButtonItem = {
let btn = UIBarButtonItem(image: UIImage(named: "mo_green_close"), style: .done, target: self, action: #selector(close))
btn.imageInsets = UIEdgeInsets(top: 0, left: -34, bottom: 0, right: 34)
return btn
}()
// viewDidLoad方法里:
navigationItem.leftBarButtonItem = backBtnItem
// WKNavigationDelegate 的代理方法
// MARK: - 加载完成
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("加载完成: didFinish")
self.title = webView.title
var btns = [backBtnItem]
if webView.canGoForward {
btns.append(forwardBtnItem)
}
if webView.canGoBack {
btns.append(closeBtnItem)
}
navigationItem.leftBarButtonItems = btns
}
最后是load
代码语言:javascript复制let request = URLRequest(url: self.url)
webView.load(request)
// 不能用 webView.loadHTMLString(self.url.absoluteString, baseURL: self.url)
// 否则evaluateJavaScript方法报错
2、WKNavigationDelegate:
这个代理在加载的各个过程中都有回调,可以根据项目需求,做响应的处理:
前面三个 `func webView(_ webView: WKWebView, decidePolicyFor ...` 的方法是一样的,只是带的参数不一样,如果实现了,就必须调用`decisionHandler`进行响应的处理,否则报错。
代码语言:javascript复制extension MOWKWebViewController: WKNavigationDelegate {
// MARK: - 判断连接是否允许跳转 navigationAction
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
print("判断连接是否允许跳转: decidePolicyFor navigationAction: (navigationAction)")
decisionHandler(.allow) // .allow or .calcel
}
// MARK: - 判断连接是否允许跳转 navigationAction preferences:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
print("判断连接是否允许跳转: decidePolicyFor navigationAction: (navigationAction) n preferences: (preferences)")
decisionHandler(.allow, preferences) // .allow or .calcel
}
// MARK: - 判断连接是否允许跳转 navigationResponse
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
print("拿到响应后决定是否跳转: decidePolicyFor navigationResponse: (navigationResponse)")
decisionHandler(.allow) // .allow or .calcel
}
// MARK: - 服务器重定向
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
print("服务器重定向: didReceiveServerRedirectForProvisionalNavigation")
}
// MARK: - 加载完成
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("加载完成: didFinish")
self.title = webView.title
}
// MARK: - 加载失败
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("加载失败: didFail error: (error)")
}
// MARK: - 即将完成
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print("即将完成: didCommit")
}
// MARK: - 加载错误
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
print("加载错误: didFailProvisionalNavigation: (error)")
}
// MARK: - 需要响应身份验证时调用(需验证服务器证书)
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
print("需验证服务器证书: didReceive challenge")
}
// MARK: - web内容进程被终止时调用(iOS 9.0之后)
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
print("进程被终止: webViewWebContentProcessDidTerminate")
}
}
3、WKUIDelegate:
一些UI展示和交互需要App这边来做,交互的结果却得反馈给JS:
代码语言:javascript复制extension MOWKWebViewController: WKUIDelegate {
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
print("alert: msg:(message)")
let alertVC = UIAlertController(title: "提示", message:message, preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "确认", style: .default, handler: { (action) in
completionHandler() // 告知JS结果
}))
self.present(alertVC, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
print("confirm: msg:(message)")
let alertVC = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (alertAction) in
completionHandler(false) // 告知JS选择结果
}))
alertVC.addAction(UIAlertAction(title: "确定", style: .default, handler: { (alertAction) in
completionHandler(true) // 告知JS选择结果
}))
self.present(alertVC, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
print("input: prompt:(prompt) defaultText:(String(describing: defaultText))")
let alertVC = UIAlertController(title: prompt, message: "", preferredStyle: .alert)
alertVC.addTextField { (textField) in
textField.text = defaultText
}
alertVC.addAction(UIAlertAction(title: "完成", style: .default, handler: { (alertAction) in
completionHandler(alertVC.textFields![0].text) // 告知JS结果
}))
self.present(alertVC, animated: true, completion: nil)
}
}
4、WKScriptMessageHandler:
接收JS那边发送过来得消息,消息有name ,是在我们初始化( configuration.userContentController.add(self, name: "moxiaoyan") )设置的。可以根据name来分别处理不同类型的消息。
代码语言:javascript复制extension MOWKWebViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// JS:window.webkit.messageHandlers.moxiaoyan.postMessage
print("didReceive message: (message.body)")
switch message.name {
case "moxiaoyan":
print("收到发给我的消息啦: (message.body)")
default: break
}
}
}
这边贴一下JS那边的实现
代码语言:javascript复制<script type="text/javascript">
// 向APP传递数据
function buttonAction() {
try {
window.webkit.messageHandlers.moxiaoyan.postMessage("JS: 客户下单啦~");
} catch (e) {
console.log(e);
}
}
</script>
二、与JS的交互
其实上面的代理实现的已经差不多了,下面补充并说明一下:
1、evaluateJavaScript的使用
1)、首先我们在`WKNavigationDelegate`的`didFinish`方法回调后,就可以使用`evaluateJavaScript`跟JS交互了:
代码语言:javascript复制 // MARK: - 加载完成
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("加载完成: didFinish")
// 调用js方法(把标题h1设置成红色)
webView.evaluateJavaScript("changeHead()", completionHandler: { (data, error) in
print("changeHead data:(String(describing: data)) error: (String(describing: error))")
})
}
我在这卡了很久,一直报错: Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=1, WKJavaScriptExceptionMessage=ReferenceError: Can't find variable: changeHead, WKJavaScriptExceptionColumnNumber=11,... 是因为加载网页的方法用的不对: 我用了: webView.loadHTMLString(_ string: String, baseURL: URL?) -> WKNavigation? 应该用: webView.load(_ request: URLRequest) -> WKNavigation?
2)、这里在app的原生页面写了两个button调用JS的方法/获取信息
代码语言:javascript复制func setupButtons() {
let baseHeight = (navigationController?.navigationBar.frame.maxY ?? 0) 20;
let getJSBtn = UIButton(type: .custom)
getJSBtn.setTitle("获取JS信息", for: .normal)
getJSBtn.setTitleColor(.red, for: .normal)
getJSBtn.addTarget(self, action: #selector(getJSInformation), for: .touchUpInside)
getJSBtn.frame = CGRect(x: 30, y: view.frame.size.height/2 baseHeight, width: 120, height: 40)
view.addSubview(getJSBtn)
let callJSFuncBtn = UIButton(type: .custom)
callJSFuncBtn.setTitle("调用JS方法", for: .normal)
callJSFuncBtn.setTitleColor(.red, for: .normal)
callJSFuncBtn.addTarget(self, action: #selector(callJSFunc), for: .touchUpInside)
callJSFuncBtn.frame = CGRect(x: 200, y: view.frame.size.height/2 baseHeight, width: 120, height: 40)
view.addSubview(callJSFuncBtn)
}
// MARK: - 获取JS title,并打印
@objc func getJSInformation() {
let js = "document.getElementsByTagName('h1')[0].innerText";
webView.evaluateJavaScript(js, completionHandler: { (data, error) in
print("getJSInformation data:(String(describing: data)) error: (String(describing: error))")
})
}
// MARK: - 调用JS的方法,并打印返回数据
@objc func callJSFunc() {
webView.evaluateJavaScript("swiftTestObject('xjf', 26)", completionHandler: { (data, error) in
print("callJSFunc data:(String(describing: data)) error: (String(describing: error))")
})
}
下面是HTML文件的代码:
代码语言:javascript复制<html lang="en">
<head>
<meta charset="utf-8" />
<title>JS交互</title>
<style>
body {
font-size:30px;
text-align:center;
}
* {
margin: 30px;
padding: 0;
}
h1{
<!-- color: red;-->
}
button {
width: 300px;
height: 50px;
font-size: 30px;
}
</style>
</head>
<body>
<h1>WKWebview与iOS交互</h1>
<h2></h2>
<button onclick="testA()">点击alert APP弹框</button>
<button onclick="testConfrim()">点击Confrim APP弹窗</button>
<button onclick="testPrompt('你是谁???')">点击Prompt APP弹窗</button>
<button onclick="buttonAction()">向APP传递数据</button>
<script type="text/javascript">
// APP调用js
function changeHead(){
document.querySelector('h1').style.color = "red"
}
// 接受APP传过来的参数
function swiftTestObject(name, age) {
var object = {name:name, age:age};
return object;
}
function testConfrim() {
confirm("确定修改数据吗?")
}
function testPrompt(title) {
prompt(title, "莫小言")
}
// 无参数函数
function testA() {
alert("我是JS中的弹窗消息");
}
// 向APP传递数据
function buttonAction() {
try {
window.webkit.messageHandlers.moxiaoyan.postMessage("JS: 客户下单啦~");
} catch (e) {
console.log(e);
}
}
</script>
</body>
</html>
还有些代理方法没有实现,没有研究,待续吧~
github Demo 地址
参考1、参考2