はじめに
こんにちは、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を呼び出す処理も追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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の下記の関数が呼び出されるようになります。
1 2 3 4 5 6 7 8 9 10 | // 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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という処理を呼び出してます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | // // 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <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とネイティブの開発が分かれてたりすると、コードの影響調査で漏れが発生し修正の際などにうまく動かなくなるのできちんと管理の必要があると感じました。