有的時候一些跨平台共用的頁面會使用網頁的方式打造,在 iOS 的世界裡我們可以使用 WKWebView
呈現網頁內容,除了單純的呈現之外,彼此的互動也是不可或缺的一環。這篇文章將會簡單介紹該如何達成網頁與 iOS 原生程式碼之間的雙向溝通。
如何從 Web 呼叫 Native 的函式
我們事先要約定好可被呼叫的函式名稱,以及可接受的參數格式,然後在 Web 呼叫 Javascript 程式如下:
window.webkit.messageHandlers.myAwesomeHandler.postMessage({
param1: "value1",
param2: "value2"
})
根據蘋果官方文件的說明,可被呼叫的函式都要放在 window.webkit.messageHandlers
物件底下,其中 myAwesomeHandler
是雙方約定好的函式名稱,postMessage
要傳的則是約定好的參數格式,最常見的格式就是 JSON Object
跟 String
。
如何在 Native 回應 Web 的呼叫
上一段提到 Web 要呼叫的 handler 是雙方約定好的函式名稱,我們可以像這樣列舉所有支援的 handlers:
private enum WebMessageHandler: String, CaseIterable {
case myAwesomeHandler
case iapHandler
case closeHandler
}
假設我們自訂一個 UIViewController
來顯示 WKWebView
,在 viewDidLoad()
裡頭可用以下做法告訴 webView 有哪些 message handlers 能夠被呼叫:
let configuration = WKWebViewConfiguration()
for handler in WebMessageHandler.allCases {
configuration.userContentController.add(self, name: handler.rawValue)
}
webView = WKWebView(frame: .zero, configuration: configuration)
view.addSubview(webView)
另外要注意的是,因為 userContentController
會持有 message handler,所以要記得在適當時機呼叫 removeScriptMessageHandler(forName name: String)
,這樣才不會產生 retain-cycle 造成 memory leak。或者也可以參考這個 stack overflow 的討論使用其他方法解決。
Note:
除了列出所有會用到的函式之外,還有另一種作法是只約定一個函式,再根據傳過來的參數判斷要作什麼事情。
別忘了在我們自訂的 UIViewController
要遵守 WKScriptMessageHandler
協定,這樣才能回應 Web 的請求:
extension MyUIViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let handler = WebMessageHandler(rawValue: message.name) else { return }
switch handler {
case .myAwesomeHandler:
guard let dict = message.body as? [String: Any],
let param1 = dict["param1"] as? String,
let param2 = dict["param2"] as? String else {
return
}
// Do some awesome stuff.
case .iapHandler:
// Open IAP page.
case .closeHandler:
// Close the web view.
}
}
}
如何從 Native 呼叫 Web 的函式
當我們處理完畢 Web 的請求之後,要如何把結果傳回去呢?一個很常見的作法是透過 JSON String,它的程式碼大致上像這樣:
let data: [String: Any] = [
"type": "userInfo",
"value": [
"name": "John Appleseed",
"id": "johnappleseed12345678"
]
]
guard let json = try? JSONSerialization.data(withJSONObject: data, options: [.withoutEscapingSlashes]),
let jsonString = String(data: json, encoding: .utf8)
else {
return
}
let javascript = "window.actions.MessageFromNative('\(jsonString)')"
webView.evaluateJavaScript(javascript, completionHandler: nil)
其中 window.actions.MessageFromNative()
是雙方事先約定好在 Web 端要實作的 Javascript 函式,如此一來 Native 只要把資料轉成 JSON String 再傳過去即可,當然 JSON 的格式也是事先約定好的。
Note:
如果是傳 JSON String 的話,很有可能需要開啟
.withoutEscapingSlashes
設定,這樣對方才不會收到反斜線跳脫字元。除了
JSONSerialization
也可以用JSONEncoder
,挑適合自己情境的來使用即可。
以上簡單的說明了 Native Code 跟 Web 之間的交互過程,以此為基礎再加上一些創造力,就可以玩出很多花樣了!