iOS WKWebView ネイティブとローカルJavascript連携


## はじめに
ネイティブとローカルのJavascript間で値の受け渡しがやりたかったので調査しました
基本ローカルではなくサーバ側にJavascriptがある場合でも同じコードで動くはずです
Swift4, iOS9以降で動作確認しています

## HTMLファイル構成
以下のようにしました。
注意として、ファイル構成はこれでなければいけないということはでは**ありません**
ディレクトリ名などわかりやすい名前をつけていただければと思います

```
WebPage
  |-css
  |  -style.css
  |-javascript
  |  -js.js
   -index.html
```
画像だと以下の感じとなります
![スクリーンショット 2017-12-16 16.09.13.png](https://qiita-image-store.s3.amazonaws.com/0/112492/100f2aa8-b526-6914-a66e-124f66c5b3ab.png)

## HTMLのソース

```HTML
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" type="text/css" href="style.css">
        <title>ページタイトル</title>
    </head>
    <body>
        <h1 id="title">タイトル</h1>
        
        <script type="text/javascript" src="js.js"></script>
    </body>
</html>
```

cssやjavascriptの読み込みが`./css/〜`や`./javascript/〜`となっておらず、同階層として扱っている点に注意してください
調べていないので間違っているかもですが、ネイティブでHTMLを読み込む際、実際のディレクトリ階層とは異なり、同階層として読み込まれているためなのかなと思っています
![スクリーンショット 2017-12-16 17.35.48.png](https://qiita-image-store.s3.amazonaws.com/0/112492/28f87737-29b3-3efd-bc57-aa5fee3e67bc.png)



## WKWebViewを使う準備
WKWebViewを使うにはWebKitをimportする必要があります

```swift
import WebKit
```

## ローカルファイル読み込み

```swift
// index.htmlのパスを取得する
let path: String = Bundle.main.path(forResource: "index", ofType: "html")!
let localHtmlUrl: URL = URL(fileURLWithPath: path, isDirectory: false)
// ローカルのHTMLページを読み込む
webView.loadFileURL(localHtmlUrl, allowingReadAccessTo: localHtmlUrl)
```

## Javascript → ネイティブ
### 宣言にWKScriptMessageHandlerを追加

```swift
class ViewController: UIViewController, WKScriptMessageHandler {
```

### WKScriptMessageHandlerプロトコルのメソッドを実装
以下のuserContentControllerメソッドでJavascriptから値を受け取れます

```swift
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        
}
```

### WKUserContentControllerを設定

```swift
let webCfg: WKWebViewConfiguration = WKWebViewConfiguration()
let userController: WKUserContentController = WKUserContentController()
userController.add(self, name: "callbackHandler")
webCfg.userContentController = userController

webView = WKWebView(frame: self.view.bounds, configuration: webCfg)
```

ポイントは`userController.add(self, name: "callbackHandler")`部分で、
ここで設定した**"callbackHandler"**がJavascriptから呼ばれると
WKScriptMessageHandler プロトコルの`userController`メソッドが実行されます

### userContentControllerの中身を実装

```swift
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    if(message.name == "callbackHandler") {
        print("\(message.body)")
    }
}
```

`message.body`にJavascriptから渡ってくる値が格納されています

### Javascript側のコード
webkit.messageHandlers.**<定義した文字列>**.postMessage()
でネイティブ側に値を渡せます。今回は定義した文字列が"callbackHandler"なので以下のようになります

```Javascript
webkit.messageHandlers.callbackHandler.postMessage("Message from Javascript");
```

## ネイティブ → Javascript
WKWebViewの`evaluateJavaScript`メソッドを使います
### 宣言にWKNavigationDelegateを追加

```swift
class ViewController: UIViewController, WKNavigationDelegate {
```

### 処理の委譲先を指定

```swift
webView.navigationDelegate = self
```

### WKNavigationDelegateの didFinish メソッドを実装

```swift
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    // Javascriptに渡す値
    let param: String = "jsCallTest"
    // Javascript側で実行する関数
    let execJsFunc: String = "test(\"\(param)\");"
    webView.evaluateJavaScript(execJsFunc, completionHandler: { (object, error) -> Void in
        // jsの関数実行結果
        // js側で戻り値を返すこともできる
    })
}
```

didFinishで行なっている理由は、HTMLのロードが終わってからでないとJavascriptのコードを実行できないためです
HTMLロード前に実行すると、Javascriptのメソッドが見つからず、evaluateJavaScriptのcompletionHandlerで`error`が返ってきます

### Javascript側のコード

```Javascript
function test(val) {
    // テスト用に id タイトルを変更
    var h1 = document.getElementById("title");
    h1.textContent = val;
}
```

以上でローカルのJavascriptと双方向で値の受け渡しができるようになりました

## 最後に
今回のコードは[こちら](https://github.com/beaa-gt/LocalWebviewExample)においています