一直聽說WKWebView
比UIWebView
強大許多,可是一直沒有使用到,今天花了點時間看寫了個例子,對其API的使用有所了解,為了日后能少走彎路,也為了讓大家更容易學習上手,這里寫下這篇文章來記錄如何使用以及需要注意的地方。
溫馨提示:本人在學習使用過程中,確實有此體會,
WKWebView
的確比UIWebView
強大很多,與JS交互的能力顯示增強,在加載速度上有所提升。
首先,我們在使用的地方引入模塊:
import Webkit
在學習之前,建議大家先點擊WKWebView
進去閱讀里面的相關API,讀完一遍,有個大概的印象,學習起來就很快了。
其初始化方法有:
public init()public init(frame: CGRect)public init(frame: CGRect, configuration: WKWebViewConfiguration)
加載HTML的方式與UIWebView
的方式大致相同。其中,loadFileURL
方法通常用于加載服務器的HTML頁面或者JS,而loadHTMLString
方法通常用于加載本地HTML或者JS:
public func loadRequest(request: NSURLRequest) -> WKNavigation?// 9.0以后才支持 @available(iOS 9.0, *)public func loadFileURL(URL: NSURL, allowingReadAccessToURL readAccessURL: NSURL) -> WKNavigation?// 通常用于加載本地HTML或者JSpublic func loadHTMLString(string: String, baseURL: NSURL?) -> WKNavigation?// 9.0以后才支持@available(iOS 9.0, *)public func loadData(data: NSData, MIMEType: String, characterEncodingName: String, baseURL: NSURL) -> WKNavigation
與之交互用到的三大代理:
WKWebView
首先,我們在ViewController
中先遵守協(xié)議:
class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate, WKUIDelegate
我們可以先創(chuàng)建一個WKWebView
的配置項WKWebViewConfiguration
,這可以設置偏好設置,與網(wǎng)頁交互的配置,注入對象,注入js等:
// 創(chuàng)建一個webiview的配置項let configuretion = WKWebViewConfiguration()// Webview的偏好設置configuretion.preferences = WKPreferences()configuretion.preferences.minimumFontSize = 10configuretion.preferences.javaScriptEnabled = true// 默認是不能通過JS自動打開窗口的,必須通過用戶交互才能打開configuretion.preferences.javaScriptCanOpenWindowsAutomatically = false// 通過js與webview內容交互配置configuretion.userContentController = WKUserContentController()// 添加一個JS到HTML中,這樣就可以直接在JS中調用我們添加的JS方法let script = WKUserScript(source: "function showAlert() { alert('在載入webview時通過Swift注入的JS方法'); }", injectionTime: .AtDocumentStart,// 在載入時就添加JS forMainFrameOnly: true) // 只添加到mainFrame中configuretion.userContentController.addUserScript(script)// 添加一個名稱,就可以在JS通過這個名稱發(fā)送消息:// window.webkit.messageHandlers.AppModel.postMessage({body: 'xxx'})configuretion.userContentController.addScriptMessageHandler(self, name: "AppModel")
創(chuàng)建對象并遵守代理:
self.webView = WKWebView(frame: self.view.bounds, configuration: configuretion)self.webView.navigationDelegate = selfself.webView.UIDelegate = self
加載我們的本地HTML頁面:
let url = NSBundle.mainBundle().URLForResource("test", withExtension: "html")self.webView.loadRequest(NSURLRequest(URL: url!))self.view.addSubview(self.webView);
我們再添加前進、后退按鈕和添加一個加載進度的控制顯示在Webview上:
self.progressView = UIProgressView(progressViewStyle: .Default)self.progressView.frame.size.width = self.view.frame.size.widthself.progressView.backgroundColor = UIColor.redColor()self.view.addSubview(self.progressView)self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "上一個頁面", style: .Done, target: self, action: "previousPage")self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "下一個頁面", style: .Done, target: self, action: "nextPage")
對于前進后退的事件處理就很簡單的,要注意判斷一下是否可以后退、前進才調用:
func previousPage() { if self.webView.canGoBack { self.webView.goBack() }}func nextPage() { if self.webView.canGoForward { self.webView.goForward() }}
當然除了這些方法之處,還有重新載入等。
對于WKWebView
,有三個屬性支持KVO,因此我們可以監(jiān)聽其值的變化,分別是:loading
,title
,estimatedProgress
,對應功能表示為:是否正在加載中,頁面的標題,頁面內容加載的進度(值為0.0~1.0)
// 監(jiān)聽支持KVO的屬性self.webView.addObserver(self, forKeyPath: "loading", options: .New, context: nil)self.webView.addObserver(self, forKeyPath: "title", options: .New, context: nil)self.webView.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: nil)
然后就可以重寫監(jiān)聽的方法來處理。這里只是取頁面的標題,更新加載的進度條,在加載完成時,手動調用執(zhí)行一個JS方法:
// MARK: - KVOoverride func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { if keyPath == "loading" { print("loading") } else if keyPath == "title" { self.title = self.webView.title } else if keyPath == "estimatedProgress" { print(webView.estimatedProgress) self.progressView.setProgress(Float(webView.estimatedProgress), animated: true) } // 已經(jīng)完成加載時,我們就可以做我們的事了 if !webView.loading { // 手動調用JS代碼 let js = "callJsAlert()"; self.webView.evaluateJavaScript(js) { (_, _) -> Void in print("call js alert") } UIView.animateWithDuration(0.55, animations: { () -> Void in self.progressView.alpha = 0.0; }) }}
我們看看WKUIDelegate
的幾個代理方法,雖然不是必須實現(xiàn)的,但是如果我們的頁面中有調用了js的alert、confirm、prompt方法,我們應該實現(xiàn)下面這幾個代理方法,然后在原來這里調用native的彈出窗,因為使用WKWebView
后,HTML中的alert、confirm、prompt方法調用是不會再彈出窗口了,只是轉化成ios的native回調代理方法:
// MARK: - WKUIDelegate// 這個方法是在HTML中調用了JS的alert()方法時,就會回調此API。// 注意,使用了`WKWebView`后,在JS端調用alert()就不會在HTML// 中顯示彈出窗口。因此,我們需要在此處手動彈出ios系統(tǒng)的alert。func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) { let alert = UIAlertController(title: "Tip", message: message, preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (_) -> Void in // We must call back js completionHandler() })) self.presentViewController(alert, animated: true, completion: nil)}func webView(webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: (Bool) -> Void) { let alert = UIAlertController(title: "Tip", message: message, preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (_) -> Void in // 點擊完成后,可以做相應處理,最后再回調js端 completionHandler(true) })) alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (_) -> Void in // 點擊取消后,可以做相應處理,最后再回調js端 completionHandler(false) })) self.presentViewController(alert, animated: true, completion: nil)}func webView(webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: (String?) -> Void) { let alert = UIAlertController(title: prompt, message: defaultText, preferredStyle: .Alert) alert.addTextFieldWithConfigurationHandler { (textField: UITextField) -> Void in textField.textColor = UIColor.redColor() } alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (_) -> Void in // 處理好之前,將值傳到js端 completionHandler(alert.textFields![0].text!) })) self.presentViewController(alert, animated: true, completion: nil)}func webViewDidClose(webView: WKWebView) { print(__FUNCTION__)}
接下來,我們看看WKScriptMessageHandler
,這個是注入js名稱,在js端通過window.webkit.messageHandlers.{InjectedName}.postMessage()
方法來發(fā)送消息到native。我們需要遵守此協(xié)議,然后實現(xiàn)其代理方法,就可以收到消息,并做相應處理。這個協(xié)議只有一個方法:
// MARK: - WKScriptMessageHandlerfunc userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { print(message.body) // 如果在開始時就注入有很多的名稱,那么我們就需要區(qū)分來處理 if message.name == "AppModel" { print("message name is AppModel") }}
這個方法是相當好的API,我們給js注入一個名稱,就會自動轉換成js的對象,然后就可以發(fā)送消息到native端。
還有一個非常關鍵的代理WKNavigationDelegate
,這個代理有很多的代理方法,可以控制頁面導航。
其調用順序為:
1、這個代理方法是用于處理是否允許跳轉導航。對于跨域只有Safari瀏覽器才允許,其他瀏覽器是不允許的,因此我們需要額外處理跨域的鏈接。
// 決定導航的動作,通常用于處理跨域的鏈接能否導航。WebKit對跨域進行了安全檢查限制,不允許跨域,因此我們要對不能跨域的鏈接// 單獨處理。但是,對于Safari是允許跨域的,不用這么處理。func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) { print(__FUNCTION__) let hostname = navigationAction.request.URL?.host?.lowercaseString print(hostname) // 處理跨域問題 if navigationAction.navigationType == .LinkActivated && !hostname!.containsString(".baidu.com") { // 手動跳轉 UIApplication.sharedApplication().openURL(navigationAction.request.URL!) // 不允許導航 decisionHandler(.Cancel) } else { self.progressView.alpha = 1.0 decisionHandler(.Allow) }}
2、開始加載頁面內容時就會回調此代理方法,與UIWebView
的didStartLoad
功能相當
func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { print(__FUNCTION__)}
3、決定是否允許導航響應,如果不允許就不會跳轉到該鏈接的頁面。
func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) { print(__FUNCTION__) decisionHandler(.Allow)}
4、Invoked when content starts arriving for the main frame.這是API的原注釋。也就是在頁面內容加載到達mainFrame時會回調此API。如果我們要在mainFrame中注入什么JS,也可以在此處添加。
func webView(webView: WKWebView, didCommitNavigation navigation: WKNavigation!) { print(__FUNCTION__)}
5、加載完成的回調
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) { print(__FUNCTION__)}
如果加載失敗了,會回調下面的代理方法:
func webView(webView: WKWebView, didFailNavigation navigation: WKNavigation!, withError error: NSError) { print(__FUNCTION__)}
其實在還有一些API,一般情況下并不需要。如果我們需要處理在重定向時,需要實現(xiàn)下面的代理方法就可以接收到。
func webView(webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) { print(__FUNCTION__)}
如果我們的請求要求授權、證書等,我們需要處理下面的代理方法,以提供相應的授權處理等:
func webView(webView: WKWebView, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) { print(__FUNCTION__) completionHandler(.PerformDefaultHandling, nil)}
當我們終止頁面加載時,我們會可以處理下面的代理方法,如果不需要處理,則不用實現(xiàn)之:
func webViewWebContentProcessDidTerminate(webView: WKWebView) { print(__FUNCTION__)}
具體代碼已經(jīng)發(fā)布到github:https://github.com/CoderJackyHuang/WKWebViewTestDemo
蘋果已經(jīng)向我們提供了WKWebView
,擁有UIWebView
的所有功能,且還提供更多的功能,明示是為了替代UIWebView
,但是WKWebView
要在ios8.0之后才能使用,因此,如果我們使用Swift來開發(fā)應用,兼容版本從8.0開始時,可以直接使用WKWebView
。
我們可以發(fā)現(xiàn),蘋果提供了更多簡便的方式讓native與js交互更加方便,通過讓native注入名稱,然后在js端自動轉換成js的對象,就可以在js端通過對象的方式來發(fā)送消息到native端。如此一來,就簡化了js與native的交互了。