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


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

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

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

##まずはWKWebviewを表示するViewControllerの宣言
~~~swift
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側で使う

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

####3.WKWebViewConfigurationにuserControllerを設定してWebviewのインスタンスを生成
ちなみにWKWebViewConfigurationには色々設定できる
詳しくは[ここ](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration)参照

~~~swift
	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して処理を分ける

~~~swift
// ネイティブ側でメッセージを受け取る
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を実行してネイティブ側にイベントを走らせる

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


##ネイティブ側からjsのfunctionを叩きたい・DOMをいじりたい
###webview.evaluateJavaScript()を使う
ただし、vuejsだとうまく動かないことがあるようです(検証中)
####1.ネイティブからjsを実行
jsのjsMethodName()というfunctionを叩きたい

~~~swift
	// 実行したい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の操作

~~~swift
	// フォームのテキストボックスに値を入力
	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に設定

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

####2. 以下を実装

~~~swift
	// 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でアメリカにある)
簡単な機能であれば十分な速度が出ていて、速度をそんなに求められないアプリならもうみんなこういう作り方でいいんじゃないかって感じました。

極端な話ですが。