UIWebView和WKWebView的区别
UIWebView
加载速度慢、占用内存多,优化困难。
研究一下再补充
WKWebView
WKWebView是ios8之后出现的,性能比UIWebView快很多,占用内存少。
特性:
- 在性能、稳定性、功能方面有很大提升。
- 允许JavaScriptNitro库加载并使用(UIWebView受限制)
- 支持了更多的HTML5特性
- 高达60fps的滚动刷新率以及内置手势
- 将UIWebViewDelegate与UIWebView重构成了14类与3个协议
WKWebView的基本用法:
- 加载网页
- 加载的状态回调
- 新的WKUIDelegate协议
- 动态加载并运行JS代码
- webview执行JS代码
- JS调用App注册过的方法
WKWebView相关类
类(使用中发现的)
- WKWebView
- WKUserScript
- WKWebViewConfiguration
- WKUserContentController
- WKScriptMessage
协议(使用中发现的)
- WKUIDelegate
- WKNavigationDelegate
- WKScriptMessageHandler
一、加载网页
加载网页或者HTML代码的方式和UIWebView相同
wkwebview = WKWebView()
let url = URL(string: urlStr)
let request = URLRequest(url: url!)
wkwebview.load(request)
二、加载的状态回调 (WKNavigationDelegate)
用来追踪加载过程(页面开始加载、加载完成、加载失败)的方法:
// 页面开始加载时调用
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("页面开始加载时调用")
}
// 页面内容开始返回调用
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print("页面内容开始返回调用")
}
// 页面加载完成之后调用
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("页面加载完成之后调用")
}
// 页面加载失败时调用
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
print("页面加载失败时调用")
}
页面跳转的代理方法:
// 在发送请求之前,决定是否跳转
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
print("在发送请求之前,决定是否跳转")
decisionHandler(.allow)
}
// 在收到响应后,决定是否跳转
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
print("在收到响应后,决定是否跳转")
decisionHandler(.allow)
}
// 接收到服务器跳转请求之后执行
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
print("接收到服务器跳转请求之后执行")
}
三、WKUIDelegate协议
这个协议主要用于WKWebView处理web界面的三种提示框(警告框、确认框、输入框),下面是警告框的例子:
/**
* web界面中有弹出警告框时使用
*
* @param webview 实现代理webview
* @param message 警告框中的内容
* @param frame 主窗口
* @param completionHandler 警告框消失调用
*/
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
print("界面弹出了警告框")
print("警告框的内容:\(message)")
completionHandler()
}
四、动态加载JS
用于在客户端内部加入JS代码,添加js_name的点击事件
let jsStr = "var js_name = document.getElementById('js_name');function haha() {window.alert(js_name.text)};js_name.addEventListener('click', haha, false);"
let script: WKUserScript = WKUserScript(source: jsStr, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let configuration = WKWebViewConfiguration()
configuration.userContentController.addUserScript(script)
wkwebview = WKWebView(frame: CGRect.zero, configuration: configuration)
wkwebview.uiDelegate = self
wkwebview.navigationDelegate = self
let url = URL(string: urlStr)
let request = URLRequest(url: url!)
wkwebview.load(request)
wkwebview.allowsBackForwardNavigationGestures = true
self.view.addSubview(wkwebview)
五、webView执行JS代码
执行已经动态添加JS的haha()方法,原有的js方法也可以调用
// wkwebview执行JS代码
wkwebview.evaluateJavaScript("haha()") { (_, _) in
print("完成执行js方法的回调")
}
六、JS调用App注册的方法
首先,WKWebView里面注册JS需要调用的方法,是通过WKUserContentController类下面的方法
open func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String)
scriptMessageHandler是代理回调,JS调用name方法后,会回调WKScriptMessageHandler代理里的这个方法
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
message包含着请求的方法的名称和参数,message.name
是调用的方法名称,message.body
是调用的方法的参数
然后JS在调用方法的时候要用下面的方法
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
注意,name(方法名)是放在中间的,messageBody只能是一个对象,如果要传多个值,需要封装成数组,或者字典。
实例代码(关键代码)
func initWKWebView() {
self.view.backgroundColor = UIColor.white
// 动态添加JS代码,实现某个控件的点击事件
let jsStr = "var stundet = {'name': 'potato'};var js_name = document.getElementById('js_name');function haha() {window.webkit.messageHandlers.testJS.postMessage(stundet);};js_name.addEventListener('click', haha, false);"
let script: WKUserScript = WKUserScript(source: jsStr, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let configuration = WKWebViewConfiguration()
configuration.userContentController.addUserScript(script)
wkwebview = WKWebView(frame: CGRect.zero, configuration: configuration)
wkwebview.uiDelegate = self
wkwebview.navigationDelegate = self
let url = URL(string: urlStr)
let request = URLRequest(url: url!)
wkwebview.load(request)
wkwebview.allowsBackForwardNavigationGestures = true
self.view.addSubview(wkwebview)
wkwebview.snp.makeConstraints { (make) in
make.top.equalTo(statusBarHeight + navigationBarHeight)
make.left.right.bottom.equalToSuperview()
}
// 注册供JS调用的方法
wkwebview.configuration.userContentController.add(self, name: "testJS")
}
func testJS(name: String) {
print("js调用原生方法, 参数name:\(name)")
}
// 实现代理
extension WebViewViewController: WKScriptMessageHandler {
// JS调用原生方法的处理
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("JS 调用了\(message.name)方法,传回参数\(message.body)")
if message.name == "testJS" {
let data: Dictionary = message.body as! Dictionary<String, Any>
testJS(name:data["name"] as! String)
}
}
}
??使用了WKScriptMessageHandler后发现会造成内存泄漏,当前页面没有销毁,deinit方法执行。
七、解决JS调用APP注册的方法造成的内存泄漏
内存泄漏主要是因为循环引用造成self无法销毁,于是定义一个弱引用的WKScriptMessageHandler
import UIKit
import WebKit
class WeakScriptMessageDelegate: NSObject, WKScriptMessageHandler {
// 弱引用
weak var delegate: WKScriptMessageHandler?
init(_ delegate: WKScriptMessageHandler) {
super.init()
self.delegate = delegate
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
self.delegate?.userContentController(userContentController, didReceive: message)
}
}
// 扩展WKUserContentController
extension WKUserContentController {
func addHandler(_ message: Any, name: String) {
if let msg = message as? WKScriptMessageHandler {
self.add(WeakScriptMessageDelegate(msg), name: name)
}
}
}
把原来注册供JS调用的方法代码改成
wkwebview.configuration.userContentController.add(WeakScriptMessageDelegate(self), name: "testJS")
或者
使用扩展添加的方法
wkwebview.configuration.userContentController.addHandler(self, name: "testJS")
在deinit中记得remove掉注册的方法
deinit {
wkwebview.configuration.userContentController.removeScriptMessageHandler(forName: "testJS")
print("WebViewViewController 销毁")
}
参考博客 - 感谢大神的贡献
好好学习,天天向上。<( ̄oo, ̄)/