iOSアプリからWKWebViewでサイトにアクセスしたときに、JSからアプリ側にデータを渡し、計測を上げるための実装をしてみました。
JS側の実装
Firebaseドキュメントの「iOS の WebView でアナリティクスを使用する」を見ればOKです。
function logEvent(name, params) {
if (!name) {
return;
}
if (window.AnalyticsWebInterface) {
// Call Android interface
window.AnalyticsWebInterface.logEvent(name, JSON.stringify(params));
} else if (window.webkit
&& window.webkit.messageHandlers
&& window.webkit.messageHandlers.firebase) {
// Call iOS interface
var message = {
command: 'logEvent',
name: name,
parameters: params
};
window.webkit.messageHandlers.firebase.postMessage(message);
} else {
// No Android or iOS interface found
console.log("No native APIs found.");
}
}
function setUserProperty(name, value) {
if (!name || !value) {
return;
}
if (window.AnalyticsWebInterface) {
// Call Android interface
window.AnalyticsWebInterface.setUserProperty(name, value);
} else if (window.webkit
&& window.webkit.messageHandlers
&& window.webkit.messageHandlers.firebase) {
// Call iOS interface
var message = {
command: 'setUserProperty',
name: name,
value: value
};
window.webkit.messageHandlers.firebase.postMessage(message);
} else {
// No Android or iOS interface found
console.log("No native APIs found.");
}
}
ネイティブ側の実装
Swift4になったことで、上記のドキュメントが少し古くなっています。
今回は特定のイベントのみを送る仕様にしたので、以下のように実装するとうまくいきました。
func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
guard let body = message.body as? [String: Any] else { return }
guard let command = body["command"] as? String else { return }
guard let name = body["name"] as? String else { return }
if command == "logEvent" {
guard let params = body["parameters"] as? [String: NSObject] else { return }
if name != "event_name" { return }
Analytics.logEvent(name, parameters: params)
}
}
userContentController
関数を使うには、WKScriptMessageHandler
プロトコルが必須なので忘れないようにしましょう。
また、loadView
で行ったWKWebViewの設定に、以下の処理を追加します。
self.webView.configuration.userContentController.add(self, name: "firebase")
何をやっているのか?
これで実装はできるのですが、一体JSとWebViewの間ではどんなやり取りがされているのでしょうか。コードに書かれた処理の流れをもう少し詳しく見てみます。
JS側の処理
まず、サイト上でイベントが起こると、logEvent
関数またはsetUserProperty
関数が呼び出されます。(いつどの関数が呼び出されるかは、自分で実装しなければいけません)
これらの関数は、message
という変数の中に、イベントの名前やパラメータを荷詰めします。
そして、
window.webkit.messageHandlers.firebase.postMessage(message);
という処理が肝です。この処理によって、message
がfirebase
という名前のメッセージハンドラでネイティブに送られます。
ネイティブ側での準備
ネイティブ側では、あらかじめJSからの通信を受け取る準備をしておく必要があります。それが
self.webView.configuration.userContentController.add(self, name: "firebase")
の部分です。
ここに使われているのは、WKUserContentController
オブジェクトです。これはJSからmessageを受け取る手段を提供してくれるものです。
ここに、JS側で指定したメッセージハンドラを追加しておく必要があります。JSの側でfirebase
という名前にしたので、ネイティブ側でもfirebase
という名前にする必要があります。でないと何も受け取れません。
ネイティブ側の処理
ネイティブ側にmessage
が届くと、userContentController
関数が呼び出されます。
ここで届いたmessage
から、command
, name
などの情報を取り出します。
上記コードでは、JS側でcommand
の値によってmessage
のキー構成が異なるので、ネイティブ側でcommand
の値によって処理を分けています。もし必要な情報が入っていなければ何もせずに関数から抜けるようになっています。
そして情報を取り出し終わると、または
でFirebaseに送ります。これでようやく、Firebaseに計測が上がります。
メッセージハンドラ名の重複に注意
今回はメッセージハンドラ名にfirebase
を使いましたが、既に他のデータを送るためにfirebase
という名前が使われていると、いらないデータも一緒に送られてくる可能性があります。
その場合、メッセージハンドラ名を変えるか、あるいは上記の実装例のように
if name != "event_name" { return }
というような、ほしいイベント以外のデータを排除する処理を付け加えると無難でしょう。