iOS Native와 JavaScript랑 대화하기

shintwl·2024년 4월 30일
1

요약

JavaScript에서 iOS Native로 데이터를 보내려면 webkit.messageHandlers.${custom_name}.postMessage,

iOS Native에서 JavaScript로 데이터를 보내려면 WKWebView의 evaluateJavaScript를 사용합니다

JavaScript로 iOS Native 호출하기

iOS 세팅

WKUserContentController의 add 메서드를 통해 자바스크립트에서 호출할 수 있는 메시지 핸들러를 설치(Install) 해줍니다

let contentController = WKUserContentController()
contentController.add(self, name: "MyJavaScriptInterfaces")

생성한 WKUserContentController 객체를 사용하는 WKWebViewConfiguration를 생성하고, WKWebViewConfiguration를 기반으로 WKWebView를 생성합니다

let configuration = WKWebViewConfiguration()
configuration.userContentController = contentController

let view = WKWebView(frame: .zero, configuration: configuration)

JavaScript에서 webkit.messageHandlers.${custom_name}.postMessage를 통해 메시지 핸들러를 호출합니다

${custom_name}는 WKUserContentController의 add 메서드 호출시 name 파라미터에 넘겨준 값과 동일해야 합니다 (이 글에서는 MyJavaScriptInterfaces 입니다)

여러 데이터를 넘겨야 하는 경우, JSON과 같은 형식으로 넘겨주면 됩니다

webkit.messageHandlers.MyJavaScriptInterfaces.postMessage({
    message: "Hello Native!",
    time: Date().toString()
});

메시지 핸들러가 호출될 경우, WKScriptMessageHandler 프로토콜의 userContentControlle:didReceive: 메서드에 메시지가 전달됩니다

WKScriptMessage의 body에 JavaScript가 전달한 데이터가 담겨 있습니다

extension ViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let body = message.body as? [String : String] else { return }
        
        let messageFromJavaScript: String = {
            return body.map { (key: String, value: String) in
                return "\(key): \(value)"
            }.joined(separator: "\n")
        }()
        
        let alertController = UIAlertController(title: "Alert", message: messageFromJavaScript, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK", style: .default)
        alertController.addAction(okAction)
        present(alertController, animated: true)
    }
}

iOS에서 JavaScript 함수 호출하기

WKWebView의 evaluateJavaScript:completionHandler: 메서드를 통해 JavaScript의 함수를 호출합니다

큰따옴표 3개를 사용해서 여러줄을 표현할 수 있습니다.
자바스크립트의 함수가 여러 파라미터를 요구하거나 문자열을 요구하는 경우 해당 방식을 사용하는 것이 편리합니다 (큰따옴표를 이스케이프 처리하거나 작은 따옴표를 사용하지 않아도 됩니다)
문법 링크

self.webView.evaluateJavaScript("""
receiveHelloFromNative("Hello Javascript!", Date())
""") { (result, error) in
    if let error = error {
        print(error)
        return
    }
    print(result ?? "no result")
}

시연 영상

전체 소스코드

import UIKit
import WebKit

final class ViewController: UIViewController {
    
    private lazy var webView: WKWebView = {
        let contentController = WKUserContentController()
        contentController.add(self, name: "MyJavaScriptInterfaces")
        
        let configuration = WKWebViewConfiguration()
        configuration.userContentController = contentController
        
        let view = WKWebView(frame: .zero, configuration: configuration)
        view.loadHTMLString("""
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=100%, initial-scale=1.0">
            <title>Hello, World!</title>
            <script>
                function sendHelloToNative() {
                    webkit.messageHandlers.MyJavaScriptInterfaces.postMessage({
                        message: "Hello Native!",
                        time: Date().toString()
                    });
                }
                function receiveHelloFromNative(message, time) {
                    document.querySelector("#result").innerHTML = (message + " at " + time.toString());
                }
            </script>
        </head>
        <body>
            <button onclick="javascript:sendHelloToNative()">
                Tap
            </button>
            <p id="result"></p>
        </body>
        </html>
        """, baseURL: nil)
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        layout()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        DispatchQueue.global().async {
            sleep(3)
            DispatchQueue.main.async {
                self.webView.evaluateJavaScript("""
                receiveHelloFromNative("Hello Javascript!", Date())
                """) { (result, error) in
                    if let error = error {
                        print(error)
                        return
                    }
                    print(result ?? "no result")
                }
            }
        }
    }

    private func layout() {
        view.backgroundColor = .white
        
        webView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(webView)
        
        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            webView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
        ])
    }
}

extension ViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let body = message.body as? [String : String] else { return }
        
        let messageFromJavaScript: String = {
            return body.map { (key: String, value: String) in
                return "\(key): \(value)"
            }.joined(separator: "\n")
        }()
        
        let alertController = UIAlertController(title: "Alert", message: messageFromJavaScript, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK", style: .default)
        alertController.addAction(okAction)
        present(alertController, animated: true)
    }
}

참고

https://developer.apple.com/documentation/webkit/wkusercontentcontroller
https://developer.apple.com/documentation/webkit/wkuserscript
https://developer.apple.com/documentation/webkit/wkwebviewconfiguration
https://oingbong.tistory.com/225
https://g-y-e-o-m.tistory.com/13
https://ios-development.tistory.com/752
https://ios-development.tistory.com/750
https://ios-development.tistory.com/700

3개의 댓글

comment-user-thumbnail
2024년 5월 3일

요즘은 정말 웹뷰가 많이 자연스럽고 많이사용되는것같더라고요...
이번에 진행하고있는 프로젝트에도 웹뷰를사용해서 데이터를 웹뷰와 연동해야햐는데
도움이 많이됐습니다!!

iOS개발자로써 웹뷰를 어떻게 생각하시는지 개인적으로 궁금하네요!

1개의 답글
comment-user-thumbnail
2024년 5월 4일

자바스크립트 언어를 불러와서 iOS에 사용하는 기술은 정말 신기하네요...
덕분에 좋은 기술 하나 알아갑니다 ㅎㅎ 좋은 글 작성해주셔서 감사합니다!! 😊😊

답글 달기