仕事でiOSアプリ側でWeb上のコンテンツを表示するアプリを作りました。側ネイティブですね。
そのときにやりたいことが全て揃っていた記事が無かったのでメモ代わりに。
##側の方でやりたいこと
- js側から処理を受け取ってネイティブ側の処理を実行したい
- ネイティブ側からjsのfunctionを叩きたい・DOMをいじりたい
- 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.name に userController.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でアメリカにある)
簡単な機能であれば十分な速度が出ていて、速度をそんなに求められないアプリならもうみんなこういう作り方でいいんじゃないかって感じました。
極端な話ですが。