ElixirDesktopからiOSのネイティブ機能を実行する

# はじめに
本記事はElixirDesktopのiOSアプリからネイティブ機能を実行できたので備忘録としてやり方を残しておきます


# 流れ
1. LiveView: phx-hook をセット
2. LiveView: push_eventで hookのthis.handleEventの関数を実行
3. JS Hook: window.webkit.messageHandlers.xx.postMessage(data)を実行
    - xxはiOS側で設定したhandler名 
4. iOS: userContentControllerでhandler名で処理を分ける
5. iOS: ネイティブ機能を実行（URLをSafariで開く)


# iOS側の設定

init() の super.initのあとに
`configuration.userContentController.add(self, name: handlerName)`
でhandler名を設定します

今回は `nativeBridge` としています

```todoapp/WebView.swift
final class WebView: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
    var webview: WKWebView
    var finish: (() -> ())?
    let handlerName = "nativeBridge"
    
    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()
        ...

        # super.initのあとに追加 
        configuration.userContentController.add(self, name: handlerName)

        ...
    }    
}
```

handlerNameをセットしたら、LiveViewから実行されたときのハンドリングを
`func userContentController`に追加します。
すでにswitch文にerrorがあるので、先程追加した nativeBridgeのケースを上辺りに追加します

なかの処理としては渡されたURLをブラウザで開くという内容です

```todoapp/WebView.swift
final class WebView: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
    var webview: WKWebView
    var finish: (() -> ())?
    let handlerName = "nativeBridge"
    ...
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        switch message.name {
        // 追加ここから
        case "nativeBridge":
            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`の`nativeBridge`に対してpostMessageを実行します

`window.webkit.messageHandlers.nativeBridge.postMessage(url);`
```assets/js/hooks.js
Hooks.NativeBridge = {
  mounted() {
    this.handleEvent("open_safari", ({ url }) => {
      window.webkit.messageHandlers.nativeBridge.postMessage(url);
    });
  },
};

export default Hooks;
```

Hooksファイルを作成したらapp.jsで読み込みます

```assets/js/app.js
import Hooks from "./hooks"; //読み込み

let liveSocket = new LiveSocket("/live", Socket, {
  hooks: Hooks, //追加
  params: { _csrf_token: csrfToken },
});

```

Hooksの設定が終わったらLiveView側で呼ぶように実装します

idを付けたタグに phx-hookでHooksのモジュールを読み込みます
phx-clickでLiveViewのイベントを発火させます

```leex:lib/xx_web/live/native_test_live.heex
<div id="native_test" phx-hook="NativeBridge" >
    <a phx-click={JS.push("open_safari", value: %{url: "https://www.phoenixframework.org/"}>
    open safari
    </a>
</div>
```

handle_eventで待ち受けし、発火したら push_eventでHooksの関数を実行します

```lib/xx_web/live/native_test_live.ex
def handle_event("open_safari", %{"url" => url}, socket) do
 {:noreply, push_event(socket, "open_safari", %{url: url})
end
```

# demo

別のアプリで試したやつですが、こんな感じに動きます

![11a731c0a32cb35198979fd48de63fdf.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/ba4d25f5-3012-411c-305b-151934c0000a.gif)

# 最後に
ベースとなる枠組みはすでにあったらので、実行する関数毎にハンドラーを設定すれば、簡単にネイティブ機能を実行できそうでした

あとはセンサーデータ等を使う双方向のやり方をマスターすればバッチリですね

# 参考サイト
- https://www.gesource.jp/weblog/?p=8528
- https://zenn.dev/yorifuji/articles/ios-wkwebview-synchronus-bridge
- https://weblabo.oscasierra.net/swift-shared-openurl-safari/