WKWebView 的一些小總結

WKWebView是  在iOS 8後推出要替代UIWebView。相對於成熟的UIWebView來說,這個後生仔在使用上仍是有點點小坑的~javascript

p1

使用

在初始化上,WKWebViewUIWebView 沒有多大的差別。前端

// 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

也就是能夠用WKWebViewConfigurationinit一個WKWebViewweb

後面再繼續港 WKWebViewConfigurationjson

那幾個協議

WKNavigationDelegate

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)
}

WKUIDelegate

主要講講網頁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)
    }
})

網頁 -> 客戶端

不像UIWebViewWKWebView沒法使用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。而且使用到一個nameTestmessageHandler。而在網頁須要進行交互的位置,則是加在一句代碼。?

<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來講,應該是NumberStringDatejsonnull

那麼問題來了。客戶端怎麼去作處理呢?

客戶端須要去實現WKScriptMessageHandler的協議方法。

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    // 建議是作好判斷,畢竟有可能 碰到多種 name 的狀況
    if message.name == "Test" {
        // 此處能夠去擼 須要 的一些交互了
        print(message.body)
    }
}

跳轉app store

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吧。

p2

相關文章
相關標籤/搜索