Swift WKWebView(二):iOS與js互動
在上一篇中我們介紹了Swift下WKWebView的基本使用方法,下面總結一下iOS與js互動的實現,最終的頁面效果如下圖所示:
其中,
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
的互動中需要用到WKWebView
的configuration
屬性,所以建立的程式碼可以如下:
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
方法
iOS
向網頁注入js
: 注入js分為在網頁document載入完畢注入和載入之前注入- 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注入的方法
。- WKUserScriptInjectionTimeAtDocumentStart
-
在文章開始的圖片中,我們看到,在
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)
}