はじめに
この記事は Elixirアドベントカレンダーのシリーズ4の23日目の記事です
今回はWebViewからネイティブ側の関数を実行する方法を解説します
例としてiOSのブラウザを起動して指定したURLを開きます
処理の流れ
- LiveView: phx-hook をセット
- LiveView: push_eventで hookのthis.handleEventの関数を実行
- JS Hook: window.webkit.messageHandlers.xx.postMessage(data)を実行
- xxはiOS側で設定したhandler名
- iOS: userContentControllerでhandler名で処理を分ける
- iOS: ネイティブ機能を実行(URLをSafariで開く)
iOS側の設定
init() の super.initのあとに
configuration.userContentController.add(self, name: イベント名)
でハンドラーを登録します
今回は openSafari
としています
final class WebView: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
var webview: WKWebView
var finish: (() -> ())?
override init() {
let preferences = WKPreferences()
let page = WKWebpagePreferences()
page.allowsContentJavaScript = true
let configuration = WKWebViewConfiguration()
configuration.limitsNavigationsToAppBoundDomains = true
configuration.preferences = preferences
configuration.defaultWebpagePreferences = page
webview = WKWebView(frame: CGRect.zero, configuration: configuration)
webview.allowsBackForwardNavigationGestures = true
webview.scrollView.isScrollEnabled = true
super.init()
+ configuration.userContentController.add(self, name: "openSafari")
...
}
}
handlerをセットしたら、LiveViewから実行されたときのハンドリングを
func userContentController
に追加します。
すでにswitch文にerrorがあるので、先程追加した openSafari
のケースを追加します
なかの処理としては渡されたURLをブラウザで開くという内容です
final class WebView: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
var webview: WKWebView
var finish: (() -> ())?
...
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
+ case "openSafari":
+ print(message.body)
+ let url = URL(string:message.body as! String)
+ if( UIApplication.shared.canOpenURL(url!) ) {
+ UIApplication.shared.open(url!)
+ }
case "error":
// You should actually handle the error :)
let error = (message.body as? [String: Any])?["message"] as? String ?? "unknown"
assertionFailure("JavaScript error: \(error)")
default:
assertionFailure("Received invalid message: \(message.name)")
}
}
}
LiveView側の設定
Hooksファイルを作成します
mount時に handleEvent open_safariで待ち受けます
ネイティブ側を呼ぶときは先程設定したhandlerName
のopenSafari
に対してpostMessageを実行します
Hooks.NativeBridge = {
mounted() {
this.handleEvent("open_safari", ({ url }) => {
window.webkit.messageHandlers.openSafari.postMessage(url);
});
},
};
export default Hooks;
Hooksの設定が終わったらLiveView側で呼ぶように実装します
idを付けたタグに phx-hookでHooksのモジュールを読み込みます
phx-clickでLiveViewのイベントを発火させます
<.gheader title="Routes">
<:actions>
<.link patch={~p"/routes/new"}>
<.icon name="hero-plus-solid" class="h-8 w-8 mr-4" />
</.link>
</:actions>
</.gheader>
+ <.button id="open_safari" phx-hook="OpenSafari" phx-click="open">
+ open MapLibre
+ </.button>
handle_eventで待ち受けし、発火したら push_eventでHooksの関数を実行します
def handle_event("open", _params, socket) do
{:noreply, push_event(socket, "open_safari", %{url: "https://maplibre.org/"})}
end
動作確認
ブラウザが起動して指定したURLが開かれるのを確認できました
最後に
本記事ではiOSのネイティブ機能をLiveView側から実行する方法を解説しました
実装の解説は本記事で終了になります
最後はElixirDesktopのアドカレまとめになります
本記事は以上になります、ありがとうございました