본문 바로가기

Swift

WKWebview를 이용한 Javascript, Swift 양방향 통신

반응형


아래 작성된 방법은 WebView 가 로드될 때 실행되는 것을 기준으로 하였으며 네이티브 앱에서 버튼을 누르거나 특정 동작을 할 때 Javascript 로 값을 전달하고 싶은 경우에는 아래 블로그에 나온 방법(evaluateJavascript)을 참고합니다.

evaluateJavascript 참고 블로그

[SWIFT] 웹뷰와 자바스크립트 연동 (Native JavaScript 통신 방법)

1. Swift → Javascript

1) Javascript의 test() 함수 실행할 때

import UIKit
import WebKit
class ViewController: UIViewController() {
    var webview: WKWebView!

    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        let userScript = WKUserScript(source: "test()", injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        let contentController = WKUserContentController()
        contentController.addUserScript(userScript)
        webConfiguration.userContentController = contentController
        webview = WKWebView(frame: .zero, configuration: webConfiguration)
        view = webview
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        guard let url = URL(string: "https://www.test.com") else { return }
        let request = URLRequest(url: url)
        webview.load(request)
    }
}

2) Swift 내의 .js 파일을 Javascript로 전달할 때

import UIKit
import WebKit
class ViewController: UIViewController() {
    var webview: WKWebView!

    override func loadView() {
        var content = ""
        if let path = Bundle.main.path(forResource: "Script", ofType: "js") {
            do {
                content = try String(contentsOfFile: path)
            } catch {
                print("error")
            }
        }

        let webConfiguration = WKWebViewConfiguration()
        let userScript = WKUserScript(source: content, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        let contentController = WKUserContentController()
        contentController.addUserScript(userScript)
        webConfiguration.userContentController = contentController
        webview = WKWebView(frame: .zero, configuration: webConfiguration)
        view = webview
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        guard let url = URL(string: "https://www.test.com") else { return }
        let request = URLRequest(url: url)
        webview.load(request)
    }
}

2. Javascript → Swift

  • WKScriptMessageHandler를 채택하고 메세지를 받고 난 이후의 동작을 지정해줍니다.
  • WKUserContentController() 에 scriptHandler 라는 이름으로 추가합니다.
  • JS에서 webkit.messageHandlers.scriptHandler.postMessage 를 이용해 값을 전달합니다.

ViewController.swift

import UIKit
import WebKit
class ViewController: UIViewController() {
    var webview: WKWebView!

    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
    let contentController = WKUserContentController()
    contentController.add(self, name: "scriptHandler")
    webConfiguration.userContentController = contentController
    webview = WKWebView(frame: .zero, configuration: webConfiguration)
    view = webview
    }

    override func viewDidLoad() {
     super.viewDidLoad()
     guard let url = URL(string: Key.url) else { return }
     let request = URLRequest(url: url)
     webview.load(request)
  }
}

extension ViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        // message.name = "scriptHandler" -> 위에 WKUserContentController()에 설정한 name
        // message.body = "searchBar" -> 스크립트 부분에 webkit.messageHandlers.scriptHandler.postMessage(<<이부분>>)
    if let body = message.body as? String, body == "searchBar" {
            guard let url = URL(string: Key.searchUrl) else { return }
            let safariVC = SFSafariViewController(url: url)
            present(safariVC, animated: true, completion: nil)
        }

        if message.body is Array<Any> {
            print(message.body)
        }
    }
}

Script.js

var search = document.querySelector('.btn-search');
search.addEventListener('click', function(){ scriptHandler("searchBar"); });

var menubar = document.querySelector('.slide-navi');
menubar.addEventListener('click', function(){ scriptHandler("menuBar"); });

function scriptHandler(message) {
    if (message == "searchBar") {
        event.preventDefault();

        try {
            webkit.messageHandlers.scriptHandler.postMessage(message);
        } catch(error) {
            alert(error);
        }
    }

    if (message == "menuBar") {
        var menu = document.querySelectorAll('.slide-navi > li > a');
        var data = [];
        for (var i = 0; i < menu.length; i++) {
            data.push({'title': menu[i].innerHTML, 'url': menu[i].getAttribute('href')});
        }
        console.log(data);
        try {
            webkit.messageHandlers.scriptHandler.postMessage(data);
        } catch(error) {
            alert(error);
        }
    }
}

3. WKWebview의 alert 띄우기를 Swift 에서 제어하기

  • viewDidLoad() 에서 webview.uiDelegate = self 설정합니다.
  • WKUIDelegate 를 채택하고 runJavaScriptAlertPanelWithMessage 파라미터를 가진 함수를 정의 합니다.

ViewController.swift

import UIKit
import WebKit
class ViewController: UIViewController() {
    var webview: WKWebView!

    override func loadView() {
        var content = "alert('test')"

        let webConfiguration = WKWebViewConfiguration()
        let userScript = WKUserScript(source: content, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        let contentController = WKUserContentController()
        contentController.addUserScript(userScript)
        webConfiguration.userContentController = contentController
        webview = WKWebView(frame: .zero, configuration: webConfiguration)
        view = webview
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        webview.uiDelegate = self
        guard let url = URL(string: Key.url) else { return }
        let request = URLRequest(url: url)
        webview.load(request)
    }
}

extension ViewController: WKUIDelegate {
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: Key.alertTitle, style: .cancel) { _ in
            completionHandler()
        }
        alertController.addAction(cancelAction)
        DispatchQueue.main.async {
            self.present(alertController, animated: true, completion: nil)
        }
    }
}


반응형