iOS
WKWebView
swift4

WKWebViewを使って側ネイティブアプリを作る

仕事でiOSアプリ側でWeb上のコンテンツを表示するアプリを作りました。側ネイティブですね。
そのときにやりたいことが全て揃っていた記事が無かったのでメモ代わりに。

側の方でやりたいこと

  1. js側から処理を受け取ってネイティブ側の処理を実行したい
  2. ネイティブ側からjsのfunctionを叩きたい・DOMをいじりたい
  3. jsのalertやconfirmを表示したい(ちゃんと処理書かないと無視された)

以上のことが出来るようにWKWebviewを実装します。

まずはWKWebviewを表示するViewControllerの宣言

import UIKit
import WebKit

class MyViewController:
    UIViewController,
    WKNavigationDelegate, // webコンテンツの読み込みなどのイベント用Delegate
    WKScriptMessageHandler, // js側からネイティブを操作する
    WKUIDelegate // jsでalertやconfirmを表示させようとした時のイベント用Delegate
{

※とりあえず必要なやつ全部継承させていますが、別にextensionとかで分けたりして書いても。

js側から値を受け取ってネイティブ側の処理を実行したい

1.WKScriptMessageHandlerを継承

2.WKUserContentControllerにcallbackを登録

→ここで登録したcallback名をjs側で使う

    let userController :WKUserContentController = WKUserContentController()
    userController.add(self, name: "nativeAction_1")
    userController.add(self, name: "nativeAction_2")

3.WKWebViewConfigurationにuserControllerを設定してWebviewのインスタンスを生成

ちなみにWKWebViewConfigurationには色々設定できる
詳しくはここ参照

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.userContentController = userController

    self.webView = WKWebView(frame: .zero, configuration: webConfiguration)

4.WKScriptMessageHandlerでJsの処理を受け取る

message.nameuserController.add(self, name: "nativeAction_1") で設定したname:が入ってるのでこれでswitchして処理を分ける

// ネイティブ側でメッセージを受け取る
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    switch message.name {
    case "nativeAction_1" :
        // 値を受け取るときはこんな感じ。
        guard let body = message.body as? NSDictionary else { NSLog("error body type"); return }
        // do something

    case "nativeAction_2" :
        // do something

    default:
        NSLog("undefined message = %s", message.name)
    }
}

5.jsを実行してネイティブ側にイベントを走らせる

webkit.messageHandlers.nativeAction_1.postMessage({ key : value });

ネイティブ側からjsのfunctionを叩きたい・DOMをいじりたい

webview.evaluateJavaScript()を使う

ただし、vuejsだとうまく動かないことがあるようです(検証中)

1.ネイティブからjsを実行

jsのjsMethodName()というfunctionを叩きたい

    // 実行したいjsのfunction
    let javaScriptString = "jsMethodName(\(value));"

    // JSを実行する
    self.webView.evaluateJavaScript(javaScriptString) { (ret, error) in
        NSLog("js completion")
        if let error = error { NSLog("error = %@", error.localizedDescription) 
    }

2.DOMの操作

    // フォームのテキストボックスに値を入力
    let getFormTextValue = "document.forms.formId.textboxId.value = \"test\""

    // JSを実行する
    self.webView.evaluateJavaScript(javaScriptString) { (ret, error) in
        NSLog("js completion")
        if let error = error { NSLog("error = %@", error.localizedDescription) 
    }

jsのalertやconfirmを表示したい

swiftで処理書いておかないと何も起こらなかった。

1. WKUIDelegateを継承して、webviewのdelegateに設定

    // WKUIDelegateを継承したMyViewController
    webView.uiDelegate = self

2. 以下を実装

    // MARK WKUIDelegate

    // alertを表示する
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {

        let alertController = 
            UIAlertController(title: "", message: message, preferredStyle: .alert)

        let okAction = 
            UIAlertAction(title: "OK", style: .default) { action in
                completionHandler()
            }

        alertController.addAction(okAction)
        present(alertController, animated: true, completion: nil)
    }

    // confirm dialogを表示する
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {

        let alertController = 
            UIAlertController(title: "", message: message, preferredStyle: .alert)

        let cancelAction = 
            UIAlertAction(title: "Cancel", style: .cancel) { action in
                completionHandler(false)
            }

        let okAction = 
            UIAlertAction(title: "OK", style: .default) {
                action in completionHandler(true)
            }

        alertController.addAction(cancelAction)
        alertController.addAction(okAction)

        present(alertController, animated: true, completion: nil)
    }

以上です。

あとがき

DelegateなどはViewControllerに継承させずにもっときれいな書き方ありますよね。
swiftを一年以上書いてなかったので、リハビリを兼ねて作ってみました。

今回はWKWebview + Vue js + node jsでSPAっぽく作ることになったのですが(更にserverはherokuでアメリカにある)
簡単な機能であれば十分な速度が出ていて、速度をそんなに求められないアプリならもうみんなこういう作り方でいいんじゃないかって感じました。

極端な話ですが。