カテゴリー: iOS

【Swift】WKWebViewでJavaScriptを利用して値の受け渡し

はじめに

こんにちは、suzukiです。今回は、WebViewで入力された値をJavaScriptを利用して値の受け渡しを行なっていきます。
ログイン情報をWeb上で入力するけど、アプリ側でも保持したいときや、WebView上のオブジェクトにデフォルトの値を渡すなどが可能です。

WebViewを利用した値の受け渡し

今回はアプリのWebViewと簡易なHTMLを利用して下記の3つの処理を行います。
・WebViewのJavaScriptから、ネイティブの処理を呼出
・ネイティブから、WebView内の要素の値を取得
・ネイティブから、WebViewのJavascriptを呼出

事前準備

まずはWKWebViewを作成します。
StoryBoardからWKWebViewを追加できるようになったのですが、今回はコードで行います。
理由として、WKWebViewのConfigurationの内容を後から変更したのですが、うまく動作しなかったためです。
問い合わせとかを見るとWKWebViewはコードで記述した方が無難そうです。

WebViewのJavaScriptから、ネイティブの処理を呼出

WebViewのConfigurationにUserContentControllerを設定して、Scriptから処理を呼び出す準備をします。
ついでにローカルのHTMLを呼び出す処理も追加します。

import UIKit
//追加
import WebKit

class ViewController: UIViewController {

    //webView
    var webView: WKWebView!
    //Scriptから下記の名前を呼び出す。
    let scriptName = "NativeInterface"
    override func viewDidLoad() {
        super.viewDidLoad()
        //ContentControllerの作成
        let userContentController: WKUserContentController = WKUserContentController()
        //JavaScriptから呼び出す処理名を設定
        userContentController.add(self, name: scriptName)
        //WKWebViewConfigurationを作成
        let config = WKWebViewConfiguration()
        //WKWebViewConfigurationにuserContentControllerを設定
        config.userContentController = userContentController
        //WebViewにContents
        webView = WKWebView(frame: .zero, configuration: config)
        //ViewをWebViewに変更
        view = webView
        
        self.LoadLocalHTML()
    }
    
    private func LoadLocalHTML(){
        guard let path: String = Bundle.main.path(forResource: "index", ofType: "html") else { return }
        let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
        webView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
        
    }
}

上記のコードにより、NativeInterfaceという名前で処理をScriptから呼びだされると
WKScriptMessageHandlerの下記の関数が呼び出されるようになります。

// JavaScriptからの呼び出しを処理するコード
extension ViewController: WKScriptMessageHandler {
    // JavaScriptからNativeの呼び出し
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if(message.name == scriptName) {
            print(message.body)
 
        }
    }
}

これでJavaScriptから下記のコードを呼び出すことで、ネイティブのコードがよばれログ出力がされます。
webkit.messageHandlers.NativeInterface.postMessage(object);

この処理でinputの内容をjavaScriptから送信しようとしたのですが、うまくいきませんでした。

ネイティブから、WebView内の要素の値を取得

下記の関数でJavaScriptを利用し、WebViewの要素を取得することができます。
func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil)

    private func getElement(){
        //inputDataのIDをもつ要素の値を取得するScript
        let executeScript = "document.getElementById('inputData').value;"
        //Scriptの実行
        webView.evaluateJavaScript(executeScript, completionHandler: { (object, error) -> Void in
            if let object = object {
                print(object)
            }
            if let error = error {
                print(error)
            }
        })
    }

ネイティブから、WebViewのJavascriptを呼出

下記の関数ではJavaScriptで定義されている関数の呼びだしも可能です。
func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil)
下記のコードではJavaScriptのcallFromNativeという処理を呼び出してます。

    // JavaScriptの処理の呼出
    private func sendMessage(str:String) {
        //試しにSend:を追加
        let message = "Send:\(str)"
        // callFromNative()という処理を呼び出す。
        let executeScript: String = "callFromNative(\"\(message)\");"
        
        webView.evaluateJavaScript(executeScript, completionHandler: { (object, error) -> Void in
            if let object = object {
                print(object)
            }
            if let error = error {
                print(error)
            }
        })
    }

コード

WebViewのボタンをタップしたら、
正常系の場合下記の順番で処理を行うコードに内容変更してます。
・WebViewのJavaScriptから、ネイティブの処理を呼出
・ネイティブから、WebView内の要素の値を取得
・ネイティブから、WebViewのJavascriptを呼出

Swift

//
//  ViewController.swift
//  JavaScriptTest
//
//  Created by MacBook007 on 2022/09/17.
//

import UIKit
//追加
import WebKit

class ViewController: UIViewController {

    //webView
    var webView: WKWebView!
    //Scriptから下記の名前を呼び出す。
    let scriptName = "NativeInterface"
    override func viewDidLoad() {
        super.viewDidLoad()
        //ContentControllerの作成
        let userContentController: WKUserContentController = WKUserContentController()
        //JavaScriptから呼び出す処理名を設定
        userContentController.add(self, name: scriptName)
        //WKWebViewConfigurationを作成
        let config = WKWebViewConfiguration()
        //WKWebViewConfigurationにuserContentControllerを設定
        config.userContentController = userContentController
        //WebViewにContents
        webView = WKWebView(frame: .zero, configuration: config)
        //ViewをWebViewに変更
        view = webView
        
        self.LoadLocalHTML()
    }
    
    private func LoadLocalHTML(){
        guard let path: String = Bundle.main.path(forResource: "index", ofType: "html") else { return }
        let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
        webView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
        
    }
}
// JavaScriptからの呼び出しを処理するコード
extension ViewController: WKScriptMessageHandler {
    // JavaScriptからNativeの呼び出し
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if(message.name == scriptName) {
            print(message.body)
            getElement()
        }
    }

    // WebViewの要素を取得
    private func getElement(){
        //inputDataのIDをもつ要素の値を取得するScript
        let executeScript = "document.getElementById('inputData').value;"
        //Scriptの実行
        webView.evaluateJavaScript(executeScript, completionHandler: { (object, error) -> Void in
            if let object = object {
                self.sendMessage(str: "\(object)")
            }
            if let error = error {
                print(error)
            }
        })
    }
    // JavaScriptの処理の呼出
    private func sendMessage(str:String) {
        //試しにSend:を追加
        let message = "Send:\(str)"
        // callFromNative()という処理を呼び出す。
        let executeScript: String = "callFromNative(\"\(message)\");"
        
        webView.evaluateJavaScript(executeScript, completionHandler: { (object, error) -> Void in
            if let object = object {
                print(object)
            }
            if let error = error {
                print(error)
            }
        })
    }
    
}

HTML

<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    </head>
    <body>
        <h1>ScriptTest</h1>
        <p>
        <button type="button" onClick='callAsynchronusNativeInterface()'>Asynchronus</button>
        </p>
        <p>
        <input type="text" placeholder="Type something..." id="inputData">
            </p>
            <p>
            <input type="text" placeholder="" id="rechieveData">
                </p>
            </body>
    <script>
        //ネイティブの処理を呼出
        function callAsynchronusNativeInterface() {
            var object = {
                "test": "test"
            }
            webkit.messageHandlers.NativeInterface.postMessage(object);
        }
        //ネイティブから処理を呼出
        function callFromNative(value) {
            var element = document.getElementById("rechieveData");
            element.value = value;
        }
    </script>
</html>

さいごに

実装してみた所管としては実装自体は簡単にできて便利だと思いました。
Webとネイティブの開発が分かれてたりすると、コードの影響調査で漏れが発生し修正の際などにうまく動かなくなるのできちんと管理の必要があると感じました。

おすすめ書籍

suzuki

シェア
執筆者:
suzuki
タグ: Swift

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前