苹果换掉UIWebView的背景:
WKWebView 是苹果在 iOS 8 中引入的新组件,目的是给出一个新的高性能的 Web View 解决方案,摆脱过去 UIWebView 的老旧笨重特别是内存占用量巨大的问题,它使用Nitro JavaScript引擎,这意味着所有第三方浏览器运行JavaScript将会跟safari一样快. 将UIWebViewDelegate和UIWebView重构成了14个类,3个协议,可以让开发者进行更加细致的配置。
这个实例,实现了多个web加载,显示加载进度,可回退,可关闭
1、初始化,和UIWebView初始化方式和加载web请求的方式一样,只是设置代理的时候发现有两个代理,WKNavigationDelegate,WKUIDelegate,从字面上看就知道,一个是和导航控制有关的,一个是和界面本身有关的,下面会具体说明
webView = WKWebView.init(frame: self.view.bounds)
webView.navigationDelegate = self
webView.uiDelegate = self
self.view.addSubview(webView)
webView.load(URLRequest.init(url: URL(string: HELP_URL)!))
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
//进度条
progress = UIProgressView.init(frame: CGRect(x:0, y:64, width: SRC_WIDTH, height:1))
progress.transform = CGAffineTransform.init(scaleX: 1, y: 0.5)
progress.progressViewStyle = .bar
self.view.addSubview(progress)
progress.progressTintColor = UIColor.blue
progress.trackTintColor = UIColor.white
progress.progress = 0
1.1、进度条,WKWebView的 estimatedProgress 属性
//MARK: 进度条
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress"{
progress.progress = Float(webView.estimatedProgress)
}
//加载完成隐藏进度条
if progress.progress == 1{
let afterTime:DispatchTime = DispatchTime.now() + 0.5
DispatchQueue.main.asyncAfter(deadline: afterTime, execute: {
UIView.animate(withDuration: 0.5, animations: {
self.progress.isHidden = true
}, completion: { (result) in
self.progress.progress = 0
})
})
}
}
2、相关代理,如何获取加载状态以及控制加载
2.1 WKNavigationDelegate
/**
* 根据webView、navigationAction相关信息决定这次跳转是否可以继续进行,这些信息包含HTTP发送请求,如头部包含User-Agent,Accept,refer
* 在发送请求之前,决定是否跳转的代理
* @param webView
* @param navigationAction
* @param decisionHandler
*/ optional public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void)
let url = navigationAction.request.url?.absoluteString
if url == "不想要跳转的url"{
decisionHandler(WKNavigationActionPolicy.cancel)
}
decisionHandler(WKNavigationActionPolicy.allow);
}/**
* 这个代理方法表示当客户端收到服务器的响应头,根据response相关信息,可以决定这次跳转是否可以继续进行。
* 在收到响应后,决定是否跳转的代理
* @param webView
* @param navigationResponse
* @param decisionHandler
*/ optional public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Swift.Void)
}/**
* 准备加载页面。等同于UIWebViewDelegate: - webView:shouldStartLoadWithRequest:navigationType
*
* @param webView
* @param navigation
*/ optional public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)
}/**
* 这个代理是服务器redirect时调用
* 接收到服务器跳转请求的代理
* @param webView
* @param navigation
*/ optional public func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!)
}
optional public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error)
}/**
* 内容开始加载. 等同于UIWebViewDelegate: - webViewDidStartLoad:
*
* @param webView
* @param navigation
*/ optional public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!)
}/**
* 页面加载完成。 等同于UIWebViewDelegate: - webViewDidFinishLoad:
* 一般在这个方法中,我们会获取加载的一些内容,比如title,另外WKWebView内部的方法canGoBack,canGoForward,都很容易让使用者控制当前页面的前进和回退交互
* @param webView
* @param navigation
*/ optional public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
self.title = webView.title
if webView.canGoBack {
self.navigationItem.leftBarButtonItems = self.leftBarbuttonItems
}
}/**
* 页面加载失败。 等同于UIWebViewDelegate: - webView:didFailLoadWithError:
*
* @param webView
* @param navigation
* @param error
*/ optional public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error)
}
//MARK: WKUIDelegate
/* webView要求一个新webView进行加载 ,如果此方法不实现,就取消加载web */
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?{
let url = navigationAction.request.url?.absoluteString
if url == "" {
let newWebV = WKWebView.init(frame: self.view.bounds, configuration: configuration)
return newWebV
}
return nil
}
//如果我们的页面中有调用了js的alert、confirm、prompt方法,
//我们应该实现下面这几个代理方法,然后在原来这里调用native的弹出窗,
//因为使用WKWebView后,HTML中的alert、confirm、prompt方法调用是不会再弹出窗口了,
//只是转化成ios的native回调代理方法
//JS 要弹出警告框, message是js传入的内容
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Swift.Void){
let alertController = UIAlertController(title: "提示", message: message, preferredStyle:.alert)
let okAction = UIAlertAction(title: "确定", style: .default) { (UIAlertAction) in
}
alertController.addAction(okAction)
// 弹出
self.present(alertController, animated: true, completion: nil)
completionHandler()
}
//JS 要弹出确认框
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Swift.Void){
}
//JS 要弹出输入框
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Swift.Void){
}
3、来说说WKWebView的configuration
WKWebView其中的一个创建方式 :
public init(frame: CGRect, configuration: WKWebViewConfiguration),很像你创建NSURLSession的时候的NSURLSessionConfiguration,WKWebViewConfiguration在Xcode中你command点进去看,里边定义的基本都是WKWebView相关的配置数据,其中有一个,在WKWebView的基本使用上还是比较常用的,在JS调native的时候很有用:
/*! @abstract The user content controller to associate with the web view.
负责和webView建立联系
*/
open var userContentController: WKUserContentController
4、 在WKWebView页面,实现JS调用native
场景:点击 计算按钮,调native方法实现阶乘算法,然后将结果返回给js
关键点:
· protocol WKScriptMessageHandler : NSObjectProtocol,WKScriptMessageHandler协议提供了一个接收消息的方法,该方法可以拦截到WKWebView上运行的JavaScript。
· html文件中的javaScript方法有特殊要求,如:
function calculateForJS(inputValue){
<!- 规定写法,实现WKScriptMessageHandler的代理方法中的JS捕捉, calculateDeal是xcode要用到的名字,inputValue是js携带的参数-->
window.webkit.messageHandlers.calculateDeal.postMessage(inputValue);
}
· native 在创建好webView后
//JS 调 OC, 设置self 为 WKScriptMessageHandler代理
//在前端开发web的时候,就要协商好,方法名 ,而且要添加window.webkit.messageHandlers.calculateDeal.postMessage(inputValue);才能在xcode中操作
// name就是html中messageHandlers后面跟的名字
webView.configuration.userContentController.add(self, name: "calculateDeal")
· JS 调 native ,WKScriptMessageHandler代理方法,通过message.name判断是哪个js,message.body就是js携带的参数
//MARK: WKScriptMessageHandler
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){
if message.name == "calculateDeal"{ //messageHandler中添加的
//计算阶乘,调JS显示结果
let inputValue = message.body as! String
if inputValue.characters.count == 0 {
return
}
let value:Int = Int(inputValue)!
var result:Int = 1
for _ in 1...value{
result = result*value
}
AppStatusPop.showInfoWithStatus(status:String(result))
//OC 调 JS,传入OC计算好的参数,带给JS
webView.evaluateJavaScript("showResult(\(result))") { (response, error) in
if error == nil{
AppStatusPop.showInfoWithStatus(status:"OC调JS成功")
}
}
}
}
5、native调js
场景:点击一个 换色 按钮, 调 native 随机 取色, 返回给js,改变按钮背景色
//MARK: WKScriptMessageHandler JS调OC
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){
if message.name == "paintACircle"{
let color = arc4random_uniform(200)
//native调js,传入色值
webView.evaluateJavaScript("painEllipse('#\(color)')", completionHandler: { (response, error) in
if error == nil{
AppStatusPop.showInfoWithStatus(status: "换色成功")
}
})
}
}
//html中的js
function painEllipse(color){
document.getElementById("changeColorBtn").style.background = color;
}