1. 程式人生 > >Swift WKWebView(二):iOS與js互動

Swift WKWebView(二):iOS與js互動

在上一篇中我們介紹了Swift下WKWebView的基本使用方法,下面總結一下iOS與js互動的實現,最終的頁面效果如下圖所示:

1

其中,js有關程式碼如下:

		function navButtonAction(name,age){
            document.getElementsByTagName('h1')[0].innerText = "ios原生呼叫js方法改變h5頁面樣式";
            var dic = {name:name,age:age};
            return dic;
        }
function alertAction(){ alert("這是js的alert方法"); } function confirmAction(){ confirm("這是js的confirm方法"); } function promptAction(){ prompt("這是js的prompt方法","default"); } function buttonAction
(){ try{ <!--js向ios傳遞資料--> window.webkit.messageHandlers.jsToIOS.postMessage("這是js傳遞到iOS的資料"); }catch (e){ } }
建立

我們可以以懶載入的方式建立一個WKWebView,並遵循代理:WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler


需要注意的地方是,由於在於js的互動中需要用到WKWebViewconfiguration屬性,所以建立的程式碼可以如下:

    lazy var wkWebView : WKWebView = {
        let config = WKWebViewConfiguration.init()
        let webView = WKWebView(frame: CGRect(x: safeAreaInsets.left
            , y: safeAreaInsets.top + 44
            , width: ScreenWidth
            , height: ScreenHeight - safeAreaInsets.top - 44 - safeAreaInsets.bottom)
            , configuration: config)
        return webView
    }()

在本文中為方便除錯,採用的是使用wkWebView載入本地html檔案的方式。另外,我們可以在測試的程式碼中使用UINavigationController ,這樣方便我們可以在導航欄上新增一些按鈕,便於我們測試,程式碼如下:

		let leftBtton = UIButton(type: .custom)
        leftBtton.setTitle("點選呼叫h5方法", for: .normal)
        leftBtton.setTitleColor(UIColor.blue, for: .normal)
        leftBtton.titleLabel?.font = UIFont.systemFont(ofSize: 16)
        let leftBarItem = UIBarButtonItem(customView: leftBtton)
        self.navigationItem.leftBarButtonItem = leftBarItem
        leftBtton.addTarget(self, action: #selector(btnClick(_:)), for: .touchUpInside)
        
        // 載入本地html檔案
        let url = Bundle.main.url(forResource: "index", withExtension: "html")
        let request = URLRequest(url: url!)
        wkWebView.navigationDelegate = self
        wkWebView.uiDelegate = self
        wkWebView.load(request)
        self.view.addSubview(self.wkWebView)

一、iOS呼叫js方法

  1. iOS向網頁注入js: 注入js分為在網頁document載入完畢注入和載入之前注入
    1. WKUserScriptInjectionTimeAtDocumentStart
      在載入之前注入,這時注入的js會在網頁的所有元素載入之前和網頁本身的js執行之前執行
      2. WKUserScriptInjectionTimeAtDocumentEnd
      在載入之後注入,當網頁的元素載入之後以及網頁本身的js執行之後才會執行注入的js
      示例程式碼如下:
    let js = "document.getElementsByTagName('h2')[0].innerText = '我是ios原生為h5注入的方法'"
    let script = WKUserScript.init(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
    self.wkWebView.configuration.userContentController.addUserScript(script)
    

    這段程式碼執行後,html介面中就會顯示出上圖中所示的 標籤我是ios原生為h5注入的方法

  2. 在文章開始的圖片中,我們看到,在iOS原生的導航欄中建立了一個點選呼叫h5方法 按鈕,我們給這個按鈕添加了點選事件後,在這個點選事件中呼叫在 js中定義的的navButtonAction(name,age) 方法,在這個方法中可以傳入兩個引數,並且可以在iOS中呼叫之後獲取這連個引數,在iOS下的單程式碼如下:

	// iOS呼叫js
    @objc func btnClick(_ button:UIButton) {

        // 呼叫js裡的navButtonAction方法,並且傳入了兩個引數,回撥裡面response是這個方法return回來的資料
        self.wkWebView.evaluateJavaScript("navButtonAction('test1',18)") { (response, error) in
            Log4jMessage(message: response)
        }
        
    }

呼叫之後列印的結果如下

2018-10-31 15:48:13.740---TestViewController.swift---btnClick---94---$:Optional({
    age = 18;
    name = test1;
})

另外,我們在js裡的navButtonAction方法,實現了方法來改變html標籤中的文字,點選按鈕之後可以看到,相關標籤頁發生了相應的變化。

二、js呼叫iOS方法

在文首的圖片中我們看到,在HTML頁面中,我們最後定義了一個按鈕點擊向iOS傳資料,在這裡,JS端想傳一些資料給iOS.那它們會呼叫下方方法來發送window.webkit.messageHandlers.<方法名>.postMessage(<資料>),
需要注意的是,上方程式碼在JS端寫會報錯,導致網頁後面業務不執行.可使用try-catch執行。在iOS中的處理方法如下。它是WKScriptMessageHandler的代理方法name和上方JS中的方法名相對應。在本文中,我們將這個name定義為jsToIOS,具體的實現程式碼如下:

self.wkWebView.configuration.userContentController.add(self, name:"jsToIOS")

對應的代理方法的實現如下:

extension TestViewController:WKScriptMessageHandler
{
  // WKScriptMessageHandler的代理方法name和JS中的方法名相對應。
  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
      Log4jMessage(message: message.name)
      Log4jMessage(message: message.body)
  }
}

呼叫之後列印的結果如下

2018-10-31 15:58:36.657---TestViewController.swift---userContentController(_:didReceive:)---106---$:jsToIOS
2018-10-31 15:58:36.658---TestViewController.swift---userContentController(_:didReceive:)---107---$:這是js傳遞到iOS的資料

三、注意事項:

由於安全機制的問題,WKWebView預設對JavaScript下alert類的方法
(包括alert(),confirm(),prompt())做了攔截,
如果要想正常使用,需要實現WKWebView的三個WKUIDelegate代理方法

alert
此方法作為js的alert方法介面的實現,預設彈出視窗應該只有提示資訊及一個確認按鈕,
當然可以新增更多按鈕以及其他內容,但是並不會起到什麼作用
點選確認按鈕的相應事件需要執行completionHandler,這樣js才能繼續執行
引數 message為 js 方法 alert() 中的,相關實現程式碼如下:

func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
       let alertCtrl = UIAlertController(title: "溫馨提示", message: message, preferredStyle: .alert)
       let action = UIAlertAction(title: "確定", style: .default) { (action) in
           completionHandler()
       
       }
       alertCtrl.addAction(action)
       self.present(alertCtrl, animated: true, completion: nil)
   }

confirm
作為js中confirm介面的實現,需要有提示資訊以及兩個相應事件, 確認及取消,並且在completionHandler中回傳相應結果,確認返回YES, 取消返回NO
引數 message為 js 方法 confirm() 中的,相關實現程式碼如下:

func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
      let alertCtrl = UIAlertController(title: "溫馨提示", message: message, preferredStyle: .alert)
      let okAction = UIAlertAction(title: "確定", style: .default) { (action) in
          completionHandler(true)
          Log4jMessage(message: "點選了確定按鈕")
      }
      let cancelAction = UIAlertAction(title: "取消", style: .default) { (action) in
          completionHandler(false)
          Log4jMessage(message: "點選了取消按鈕")
      }
      alertCtrl.addAction(okAction)
      alertCtrl.addAction(cancelAction)
    

prompt
作為js中prompt介面的實現,預設需要有一個輸入框一個按鈕,點選確認按鈕回傳輸入值
當然可以新增多個按鈕以及多個輸入框,不過completionHandler只有一個引數,如果有多個輸入框,需要將多個輸入框中的值通過某種方式拼接成一個字串回傳,js接收到之後再做處理
引數 prompt 為 prompt(, ),相關實現程式碼如下:

func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
      
      let alertCtrl = UIAlertController(title: "溫馨提示", message: "", preferredStyle: .alert)
      alertCtrl.addTextField { (textField) in
          textField.text = defaultText
      }
      
      let okAction = UIAlertAction(title: "確定", style: .default) { (action) in
          completionHandler(alertCtrl.textFields?[0].text)
          Log4jMessage(message: "點選了確定按鈕"+" 內容: " + (alertCtrl.textFields?[0].text ?? "") )
      }
      let cancelAction = UIAlertAction(title: "取消", style: .default) { (action) in
          completionHandler(alertCtrl.textFields?[0].text)
          Log4jMessage(message: "點選了取消按鈕"+" 內容: " + (alertCtrl.textFields?[0].text ?? ""))
      }
      alertCtrl.addAction(okAction)
      alertCtrl.addAction(cancelAction)
      self.present(alertCtrl, animated: true, completion: nil)
  }