맛동산이

Swift) MKWebView를 통한 javascript 통신 본문

앱/Swift

Swift) MKWebView를 통한 javascript 통신

진ddang 2024. 5. 13. 09:35

MKWebView란

기존의 웹뷰는 UIWebView를 사용했지만, 현재는 deprecated되었다.

따라서 WKWebView를 통해서 iOS에서는 웹뷰를 로딩할수 있다.

WKWebView는 javascript를 통해서 네트워킹을 할수 있으며, 이는 비동기적으로 동작하게 된다.


사전에 필요한 개념들을 미리 정리해보자!

MKWebViewDataStore?

MKWebView는 쿠키, 디스크 그리고 메모리캐시등 다양한 웹뷰의 데이터를 저장하고, 처리하는 클래스이다.

즉 해당 클래스를 통해서

  • 웹사이트에서 사용하는 쿠키 관리
  • 웹사이트가 저장하는 데이터 타입 확인
  • 웹사이트에 저장된 데이터 제거

등을 할수 있다.

실제로 캐시를 제거하는 코드

WKWebsiteDataStore.default().removeData(ofTypes:
[WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache],
modifiedSince: Date(timeIntervalSince1970: 0)) { }

WKUserContentController

웹뷰와 javascript코드 사이의 인터페이스를 관리하는 클래스 객체,

이 객체를 통해서는 다음과 같은 일을 하게 된다.

  • 웹뷰에서 사용될 자바스크립트 코드를 주입
  • 네이티브 코드를 통해서 불려질 자바스크립트 코드 만들기

등등을 할수 있다.

WKWebViewConfiguration

웹뷰가 어떻게 생성될것인지를 저장하는 클래스 객체, 해당 configuration을 통해서 웹뷰는 다음과 같은 것을 설정하게 된다

  • The initial cookies to make available to your web content
  • Handlers for any custom URL schemes your web content uses
  • Settings for how to handle media content
  • Information about how to manage selections within the web view
  • Custom scripts to inject into the webpage
  • Custom rules that determine how to render content

웹뷰 로드하기

웹뷰는 말그대로 웹을 로드하는것이기 때문에 url을 통해서 화면을 그려준다.

따라서 로드하는 코드는 다음과 같다.

    func webViewInit(){

        if let url = URL(string: "<https://m.officecheckin.com>") {
            let request = URLRequest(url: url)
            webView.load(request)
        }
        
    }

하지만 이때 저 웹뷰를 그냥 그리는 것 뿐 아니라 javascript로 통신하기 위해서는 configuration, datastore, userContentController등을 정의해서 넣어주어야 한다.

웹뷰 생성시 WKUserContentController 생성하기

class WebViewController: UIViewController {

	var userContentController = WKUserContentController.init()
    
    override func viewDidLoad() {
        super.viewDidLoad()
		let configuration = WKWebViewConfiguration.init()
		configuration.userContentController = self.userContentController
		configuration.userContentController.add(self, name: "YourInterfaceName")
		let webView = WKWebView.init(frame: view.frame, 
        					 configuration:configuration)
	}
    
}
출처: <https://zetal.tistory.com/entry/WKUserContentController> [개발자는 왜 체크인가:티스토리]

이렇게 userContentController를 생성할때, javascript(웹) 에 정의되어 합의되어있는 인터페이스 메소드 명을 작성하게 된다. (yourInterfaceName) 에다가

자바스크립트를 통한 데이터 통신(WKScriptMessageHandler)

웹뷰를 통한 통신은 크게 두가지가 있다.

  • 앱 → 웹 : evaluateJavaScript
  • 웹 → 앱 : WKScriptMessageHandler

먼저 통신을 하기 위해서는 javascript(웹) 과 앱의 통신 interface를 맞추어야 한다.

이게 무슨말 이냐하면, 우리는 앱에서 어떤 버튼이나 액션을 할것이고, 이를 통해서 웹뷰에 정의되어 있는 메소드를 호출할건데, 어떤 메소드가 호출될것인지 동일한 이름으로 저장하고,

혹은 어떠한 웹에서 처리된 데이터를 앱으로 내려주어야하는데 이때 데이터의 형태나 이런것들을 정의해야 한다는 말이다.

앱 → 웹 데이터 통신

extension WebViewController {

    /// JavaScript 메소드를 호출하는 메소드
    func callJavaScript(script: String, completionHandler: @escaping (Any?, Error?) -> Void) {
        // evaluateJavaScript를 사용하여 웹 페이지 내의 JavaScript 함수를 호출할 수 있다.
        webView.evaluateJavaScript(script) { (result, error) in
            if let error = error {
                print("Error calling JS function: \\(error)")
                completionHandler(nil, error)
            } else if let result = result {
                print("Received result from JS function: \\(result)")
                completionHandler(result, nil)
            }
        }
    }
}

webView.evaludateJavaScript()를 통해서 자바스크립트에 이미 정의되어 있는 메소드명을 string으로 받아서 처리하게 된다.

이렇게 하면, 실제로 자바스크립트에서 string으로 받은 그 이름의 메소드가 실행된다. (웹뷰에서 실행된다는 말임)

웹 → 앱 데이터 통신

웹에서 앱으로의 통신은 WKScriptMessageHandler 프로토콜을 채택한 WKWebView를 통해서 할수있다.

해당 프로토콜을 채택한 경우 있는 userContentController()라는 메소드를 정의하는것으로 할수 있는데 예시코드는 다음과 같다.

	func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {

        let name = message.name
        guard let body = message.body as? [String: Any] else {
            return
        }
        guard let command = body["command"] as? String else {
            return
        }
        
        if name == "YourInterfaceName" {
            if command == "popOpened" {
                print("call back")
            }
        }
    }
출처: <https://zetal.tistory.com/entry/WKUserContentController> [개발자는 왜 체크인가:티스토리]

혹은 간단하게

if message.name == "messageHandler", let messageBody = message.body as? [String: Any] {
	print("Received message from JavaScript: \\(messageBody)") // 웹으로부터 받은 메시지를 파싱하여 print를 호출하는 코드.
}

여기에서 사용하는 message.name이란, 위에서 contentController에서 add해준 그 yourinterfaceName이다.

이렇게 해당 동작을 하면, 웹뷰 내부에서 (javascript) 정의되어 있는 yourInterfaceName인, 메소드가 호출이 되고 그 결과가 우리 앱으로 들어오게 된다.

이때 통신은 JSON이니까 당연히 body가 있을수 있고, 그 바디에 넣는 데이터가 위의 body부분이다.

이렇게 바디를 통해서 데이터를 전달해주면, 웹뷰에서도 정의된 데이터를 넘겨줄것이고,

그것을 받아서 처리해주면 된다.


정리

  • WKWebView를 통해서 javascript와 통신하여 웹뷰를 화면에 구현할수 있다.
  • WKWebView를 생성할때는 크게 3개의 객체 WKWebViewDataStore, WKUserContentController, WKWebViewConfiguration 를 통해서 생성할, WebView를 정의하고, 설정하게 된다.
  • 웹 → 앱의 통신에서는 WKJavaScriptMessageHandler라는 프로토콜에 있는 메소드를 통해서 통신하게 되는데 이때 위에서 정의한 이름으로 메소드를 호출한다.
  • 앱 → 웹의 통신에서는 evaluateJavaScript라는 메소드를 통해서 웹뷰에 정의된 메소드를 실행하게 된다.

위와 같은 동작을 연속적으로 실행하게 되는데 이때, 주고받는 데이터의 타입과 메소드명을 웹쪽 개발자와 소통해서 Interface를 저장해야지 문제없이 동작하게 할수 있다.


참고자료

[iOS]WKWebView를 이용한 하이브리드 앱(Hybrid App) 제작하기

[iOS] WKWebView 기본 메서드 뽀개기! - HoonIOS

[iOS] 웹뷰의 델리게이트 메서드 ( WKNavigationDelegate & WKUIDelegate) - HoonIOS

 

아 그리고,,, tistory open api 가 끝나서, n2t나 nagrai같은 노션에서 옮기는 작업을 수동으로해야해서 귀찮아짐.....
너ㅓㅓㅓㅓㅓㅓㅓㅓㅓㅓㅓ무 귀찮다.

반응형