WKWebView
是 在iOS 8
後推出要替代UIWebView
。相對於成熟的UIWebView
來說,這個後生仔在使用上仍是有點點小坑的~javascript
在初始化上,WKWebView
和 UIWebView
沒有多大的差別。前端
// WKWebView let wkWeb = WKWebView(frame: view.bounds) // 一些代理 wkWeb.navigationDelegate = self wkWeb.uiDelegate = self // UIWebView let web = UIWebView(frame: view.bounds) // 一些代理 web.delegate = self
兩者在初始化上仍是蠻像的。一個圖樣的我。(逃java
仔細翻開了WKWebView
,發現其還提供一個初始化方法。git
public init(frame: CGRect, configuration: WKWebViewConfiguration)github
也就是能夠用WKWebViewConfiguration
去init
一個WKWebView
。web
後面再繼續港 WKWebViewConfiguration
。json
WKNavigationDelegate
的協議方法仍是挺多的。swift
// 1)接受網頁信息,決定是否加載仍是取消。必須執行肥調 decisionHandler 。逃逸閉包的屬性 func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { print("\(#function)") } // 2) 開始加載 func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { print("\(#function)") } // 3) 接受到網頁 response 後, 能夠根據 statusCode 決定是否 繼續加載。allow or cancel, 必須執行肥調 decisionHandler 。逃逸閉包的屬性 func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { print("\(#function)") guard let httpResponse = navigationResponse.response as? HTTPURLResponse else { decisionHandler(.allow) return } let policy : WKNavigationResponsePolicy = httpResponse.statusCode == 200 ? .allow : .cancel decisionHandler(policy) } // 4) 網頁加載成功 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { print("\(#function)") } // 4) 加載失敗 func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { print("\(#function)") print(error.localizedDescription) }
主要講講網頁js
的一些函數——api
alert()
閉包
comfirm()
prompt()
在UIWebView
中,js
使用上述三個函數,是能夠成功彈出的。可是在WKWebView
中,使用着三個函數,是不會用任何反應的。緣由是,把這三個函數都分別封裝到WKUIDelegate
的方法中。但js
使用這些函數時,那麼客戶端會在如下幾個協議方法中,監測到發送過來的信息,而後須要用原生代碼去實現一個alert
。累cry~~~
// MARK: alert func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { let alert = UIAlertController(title: "這是本地代碼彈窗", message: message, preferredStyle: .alert) lert.addAction(UIAlertAction(title: "ok", style: .cancel, handler: { _ in // 必須加入這個 肥調,否則會閃 (逃 completionHandler() })) present(alert, animated: true, completion: nil) } // MARK: comfirm func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let alert = UIAlertController(title: "這是本地代碼彈窗", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "❤️", style: .default, handler: { _ in completionHandler(true) })) alert.addAction(UIAlertAction(title: "不❤️", style: .default, handler: { _ in completionHandler(false) })) present(alert, animated: true, completion: nil) } // MARK: prompt func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { let alert = UIAlertController(title: "這是本地代碼彈窗", message: prompt, preferredStyle: .alert) alert.addTextField { textField in textField.placeholder = defaultText } alert.addAction(UIAlertAction(title: "ok", style: .default, handler: { _ in completionHandler(alert.textFields?.last?.text) })) present(alert, animated: true, completion: nil) }
有些網頁在客戶端上顯示,會出現一些不適配的狀況。使用UIWebView
的話,咱們能夠用使用其的scaleToFit
屬性。即webView.scaleToFit = true
。可是在WKWebView
裏,並無這個屬性,咱們只能使用到JS注入
進行修改。
// 這句至關於給網頁注入一個 <meta> 標籤,<meta name="viewport" content="width=device-width"> let jsToScaleFit = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);" let scaleToFitScript = WKUserScript(source: jsToScaleFit, injectionTime: .atDocumentEnd, forMainFrameOnly: true) let userController = WKUserContentController() userController.addUserScript(scaleToFitScript) let config = WKWebViewConfiguration() config.userContentController = userController let wkWeb = WKWebView(frame: view.bounds, configuration: config!)
不過,仍是不太推薦客戶端去注入適配代碼。最好仍是告知前端,讓他們去搞定這問題。我的以爲,兩端少點干涉仍是比較好滴~~~ (逃
有時間,咱們須要客戶端去調用前端的一些代碼。e.g.
// 好比獲取網頁內容高度 let jsToGetWebHeight = "document.body.offsetHeight" wkWeb?.evaluateJavaScript(jsToGetWebHeight, completionHandler: { (data, error) in print(error?.localizedDescription ?? "執行正確") // data 是一個 any 類型,所以須要作好類型判斷 if let webHeight : CGFloat = data as? CGFloat { print(webHeight) } })
不像UIWebView
,WKWebView
沒法使用JaveSciptCore
。咱們須要使用到WKScriptMessageHandler
這個協議去進行一系列交互。
首先,在初始化階段,須要使用到WKWebViewConfiguration
。放個文檔註釋先。
/*! @abstract Adds a script message handler.
@param scriptMessageHandler The message handler to add.
@param name The name of the message handler.
@discussion Adding a scriptMessageHandler adds a function window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all frames. */open func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String)
name
是客戶端自定義好的一個字符串類型的命名空間。能夠有多個name
。網頁的js
代碼,須要在進行交互的地方,使用上
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
來發送消息。
上個栗子?。
let conifg = WKWebViewConfiguration() config.userContentController.add(self, name: "Test") let webView = WKWebView(frame: view.bounds, configuration: config)
這是客戶端使用WKWebViewConfiguration
去初始化一個WKWebView
。而且使用到一個name
爲Test
的messageHandler
。而在網頁須要進行交互的位置,則是加在一句代碼。?
<script type="text/javascript"> function test() { var message = { action: "test", params: null, callback: "callback()" }; window.webkit.messageHandlers.Test.postMessage(message); } </script>
對應messageBody
載體的格式,貌似沒有多大的規定。能夠爲NSNumber
, NSString
, NSDate
, NSArray
, NSDictionary
,甚至是NSNull
。也就是對應js
來講,應該是Number
,String
,Date
,json
,null
。
那麼問題來了。客戶端怎麼去作處理呢?
客戶端須要去實現WKScriptMessageHandler
的協議方法。
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { // 建議是作好判斷,畢竟有可能 碰到多種 name 的狀況 if message.name == "Test" { // 此處能夠去擼 須要 的一些交互了 print(message.body) } }
WKWebview
是沒法直接跳轉app store
。不明白爸爸爲何要這樣。反正他高興就好。。。。
那麼若是PM
硬要跳轉app store
的話,有兩種方式——
1) 砍死PM
。。(ps: 我的極度推薦方式一)
2)那麼你只能苦逼碼碼去解決了。
在WKNavigationDelegate
中,當接受到網頁信息的時候,也就是——
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { let request = navigationAction.request if let url = request.url { if url.host == "itunes.apple.com" { UIApplication.shared.openURL(url) decisionHandler(.cancel) } } decisionHandler(.allow) }
某天,PM
跑過來跟你港,想要點擊一個網頁超連接,而後客戶端去push controller
,而不是在原頁面上刷新。。。
使用UIWebView
的時候,其實挺方便的,只須要在UIWebViewDelegate
的一個方法中去監聽作判斷就好。吶。看?。
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool { if navigationType == .linkClicked { guard let url = request.url?.absoluteString else { return true } // 此處 push 一個 新的 controller 吧 return false } return true }
可是,WKWebView
呢?
須要在WKNavigationDelegate
協議方法中,當接收到網頁信息的時候——
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { let request = navigationAction.request if let url = request.url { // 二、檢測打開 <a href> 標籤 。若是要打開一個 新web,那麼須要 <a href="xx" target="_blank" >,若無 target="_blank",則只會在原web基礎上 reload if navigationAction.targetFrame == nil { // 這裏作 push 新的 webview 操做 } } decisionHandler(.allow) }
暫時擼到這裏吧。估計還有一些坑。後續繼續踩,繼續更新吧。
最後,上個demo吧。