2022. 11. 28. 13:57ㆍiOS/iOS
wkwebView 통신에 대해 공부하면서 javascript통신이 왜 필요한지에 대해 이해가 된 내용을 바탕으로, evaluateJavaScript와 WKScriptMessageHandler에 대해서 정리합니다.
웹뷰 화면에서 어떤 버튼을 눌렀을때 외부 브라우저로 이동을 한다던가 하는 기능을 ios개발자가 구현한다고 했을때, 처음 버튼 구현부터 iOS개발자가 했다면 모르겠지만, 그렇지 않은 경우 ibaction 함수를 이용하지 않습니다.
다시말해, 온전한 코드를 iOS개발자가 짰다면 모를까(A 버튼을 누르면 ibaction함수로 연결해서 외부 브라우저로 이동하게끔 코드를 짜는것처럼, 모든 flow가 ios에서 이뤄졌었던 코드면 모를까)
그게 아니라 그냥 뷰컨에 웹뷰만 붙였을뿐, 웹뷰에서 어떤 작업이 일어나는지 iOS개발자는 알 길이 없습니다.
왜냐하면 버튼 구현, 버튼을 클릭했을때 실행되는 함수구현까지 iOS개발자가 구현한것이 아니기 때문입니다.
그렇다면, 프론트 개발자랑 협업해서 하는 프로젝트이므로
웹뷰에서 버튼을 클릭했다면, 화면단을 구현한 프론트 개발자는 특정 함수가 호출되도록 구현을 해야합니다.
쉽게말해서..
😌자스개발자: 웹뷰에서 외부 링크로 연결되는 버튼을 클릭하면 자스에서 아래 값을 내려드릴게요.
😎ios개발자: 좋아요 그럼 보내주신 값을 이용해서 "URL_LOAD"값을 받으면, 보내주신 Url로 이동하도록 구현하겠습니다.
이런 플로우로 업무가 진행되어야 함을 의미합니다.
그러기위해선 json 파싱을 통해 서버에서 내려받은 값이 "URL_LOAD"라면 switch문법 등을 이용하여 분기처리될 수 있도록 하고, 이제 비로소 url로 이동하는 코드를 ios개발자가 작성해줘야합니다.
하지만 위의 예처럼 url값을 단순히 전달받는경우, 서버로 부터 저 위의 값을 받자마자 바로 파싱해서 url로 이동하는 코드만 작성해주고, 외부 브라우저를 열어서 이동만 시켜주면 되기 때문에 evaluateJavaScript를 사용할 이유가 없었습니다. 즉, 다시 따로 서버한테 보내줄 값이 없었다는 의미입니다.
그래서 한가지 가정을 세워봅니다. 이러한 상황에서 서버와 통신하는 기능이 추가 되었다고 생각해보는 것입니다.
예를들어, 제대로 외부브라우저로 넘어갔을때 서버쪽으로 보내는값이 "SUCCESS"값, "FAIL"값인지에 따라서 서버가 웹뷰 화면에 제대로 이동했다, 아니다를 나타내는 팝업을 띄운다고 합니다.
그러면 이러한 상황에서는 외부 브라우저로 url이 제대로 이동했는지, 실패했는지 알기 위해, iOS개발자는 서버쪽으로 "SUCCESS"든, "FAIL"이든 보내줘야 할 것입니다.
통신! 이 필요한 순간입니다.
그래서 javascript와의 통신이 필요한것이고, ios에서 javascript로 값을 넘겨주는것이 무엇인지, javascript에서 ios로 값을 넘겨줄땐 어떤것을 사용하는지 아는것이 매우 중요합니다.
javascript에 값을 넘겨줄때)
WebView에서 javascript 코드를 호출하는 방법
-> evaluateJavaScript(_:completionHandler:) 메서드를 사용해야합니다.
func executeScript(_ script: String) {
if script.count < 100 {
DLog.d("### executeScript: \\(script)")
}
//script는
//웹뷰에서 javascript코드를 호출하는 중
webView.evaluateJavaScript(script) { (result, error) in
DLog.d("[MAIN] evaluate: result: \\(String(describing: result)), error: \\(String(describing: error))")
if let error = error {
DLog.d("[MAIN] executeScript: ERR.SCRIPT: \\(script)")
self.showDebugAlert("ERR: \\(error.localizedDescription) in \\(script)")
}
}
}
webView.evaluateJavaScript(script) 한줄을 주목해봅니다.
script에 들어가야하는 값은 아래와 같은 형식이 될것입니다.
아래에 임의로 적어둔 함수는 NEXTPAGE 인데 이 값은 서버 개발자가 보내달라고 하는 함수이름과 맞추면 되므로 서버연동규격서를 확인해야합니다.
NEXTPAGE({"STATUS":"SUCCESS"})
위와 같은 함수를 evaluateJavaScript함수에 담아보내면 서버쪽으로 전달될 것이고, javascript에서 작성된 NEXTPAGE함수가 실행될 것입니다.
서버에서 받아온값을 가지고 기능을 개발할 때)
javascript로부터 네이티브에 메시지를 전달받는 경우
-> WKScriptMessageHandler 프로토콜 채택해서 사용해야합니다.
extension TSMainViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
DLog.d("[MAIN] didReceive... name: \\(message.name)")
DLog.d("[MAIN] didReceive... body: \\(message.body)")
if message.name != TSConst.TS_MAIN_SCHEME {
DLog.w("[MAIN] didReceive... Not a valide protocol!")
return
}
//iOS13.7에서는 Stringify해서 전달해야한다. 이 경우, 14.x에서는 json으로 전달된다.
if let bodyStr = message.body as? String,
let dict = convertToDictionary(text: bodyStr) {
DLog.d("[MAIN] didReceive... stringToDict: \\(dict)")
self.processWebContent(dict)
}
else if let dict = message.body as? [String: Any] {
DLog.d("[MAIN] didReceive... dict: \\(dict)")
self.processWebContent(dict)
}
else {
DLog.w("[MAIN] didReceive... Not a valide body!")
}
}
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
}
catch {
print(error.localizedDescription)
}
}
return nil
}
}
didReceive... body: {"action":"GET_APP_VERSION","callback":"GET_APP_VERSION"}
didReceive... stringToDict: ["action": GET_APP_VERSION, "callback": GET_APP_VERSION]
디버그에 찍힌 내용을 보면 위와 같은 식으로, action이나 callback 값을 가지고 분기처리하여,(파일 다운로드 기능인지, 외부앱이동 기능인지 등등) 원하는 기능을 구현하면 됩니다.