IWKWebView 코드에 자바스크립트 핸들러 추가
핸들러에 함수명을 적어주는게 보편적이지만 JS -> Native 많은 함수를 호출할 경우 여러개를 등록해야되서 스키마와 자바스크립트 두가지로만 구분하고 Web에서 파라메터를 이용해서 함수명을 호출하게 구현했다. (test.html 예제를 보면 알 수 있음)
func configuration() {
.... 생략 ....
// Scheme 및 JS 함수 핸들러 등록
let contentController = WKUserContentController()
contentController.add(self, name: WebMessageHandler.customScheme.rawValue)
contentController.add(self, name: WebMessageHandler.javascript.rawValue)
let config = WKWebViewConfiguration()
config.processPool = processPool!
config.preferences.javaScriptCanOpenWindowsAutomatically = true
config.preferences.javaScriptEnabled = true
// WKUserContentController 자바스크립트 핸들러 등록 WebView 연결
config.userContentController = contentController
.... 생략 ....
}
IWKwebViewDelegate
@objc protocol IWKWebViewDelegate: AnyObject {
/// 웹뷰 로딩 완료
@objc func webviewDidFinishNavigation(webView: WKWebView);
/// 웹뷰 새창 추가 완료
@objc optional func webviewDidFinishAddNewWebView(webView: WKWebView);
/// 웹뷰 로딩 실패
@objc optional func webViewdidFailProvisionsNavigation(webView: WKWebView);
/// 자바스크립트 메세지 핸들러
@objc optional func javascriptMessageHandler(function: String, param: Dictionary<String, Any>);
/// 커스텀스키마 메세지 핸들러
@objc optional func schemeMessageHandler(schemeUrl: URL);
}
WKScriptMessageHandler 프로토콜 구현
// MARK: WKScriptMessageHandler
extension IWKWebView: WKScriptMessageHandler {
/*
WKScriptMessageHandler
참고자료 - https://swieeft.github.io/2020/02/23/ImportSMSiOS.html
JS -> Native 연동
Web -> Scheme 연동
위에서 등록한 customScheme / javascript 핸들러 메세지 받는다.
*/
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
let handlerName = WebMessageHandler(rawValue: message.name)
// Scheme 및 JS -> Native 구분
switch handlerName {
case .customScheme:
if let bodyString = message.body as? String {
if let url = URL(string: bodyString) {
delegate?.schemeMessageHandler?(schemeUrl: url)
}
}
case .javascript:
if let bodyString = message.body as? String {
let params = bodyString.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: "\\", with: "")
if let jsDic = convertToDictionary(text: params) {
/* 함수명 파라메터
형식 - {"login" : {"idx":1, "name":"twok"}}
{"logout" : {}}
Web -> Native javascript 함수 호출 처리
참고자료 : https://life-shelter.tistory.com/315
*/
if let jsFunc = jsDic.keys.first?.description {
if let params = jsDic[jsFunc] as? Dictionary<String, Any> {
delegate?.javascriptMessageHandler?(function: jsFunc+":", param: params)
}
}
}
}
default: break
}
}
// Json String -> Dictionary 형태로 변경
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
}
/// Native -> Javascript 호출
func evaluateJavaScript(javaScriptFuncString: String, param: String, completionHandler: ((Any?, Error?) -> Void)?) {
log(direction: .SEND, ofType: self, datas: javaScriptFuncString, param)
if let lastWebView = webViewList.last {
lastWebView.evaluateJavaScript("\(javaScriptFuncString)(\(param))", completionHandler: completionHandler)
}
}
}
MainViewContoller.swift
extension URL {
/// URL의 파라메터 값을 Dictionary로 변경
var queryDictionary: [String: String]? {
guard let query = self.query else { return nil}
var queryStrings = [String: String]()
for pair in query.components(separatedBy: "&") {
let key = pair.components(separatedBy: "=")[0]
let value = pair
.components(separatedBy:"=")[1]
.replacingOccurrences(of: "+", with: " ")
.removingPercentEncoding ?? ""
queryStrings[key] = value
}
return queryStrings
}
}
// Native -> JS 델리게이트
extension MainViewController: MainViewControllerDelegate {
func webviewEvaluateJavaScript(jsFunctionString: String, param: String, completionHandler: ((Any?, Error?) -> Void)?) {
self.webBaseView.evaluateJavaScript(javaScriptFuncString: jsFunctionString, param: param) { result, error in
completionHandler?(result, error)
}
}
}
class MainViewController: UIViewController {
@IBOutlet weak var webView: IWKWebView!
// JS -> Native Message Handler Class
let javascriptMessageHandler = JavascriptMessageHandler()
let customSchemeMessageHandler = CustomSchemeMessageHandler()
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
// webView.webViewLoad(urlString: "https://www.naver.com", httpMethod: .GET)
webView.webViewHtmlLoad()
}
}
//MARK: IWKWebViewDelegate
extension MainViewController: IWKWebViewDelegate {
//웹 페이지 로딩 완료
func webviewDidFinishNavigation(webView: WKWebView) {
let urlString: String = webView.url?.absoluteString ?? ""
let string = String(describing: urlString.removingPercentEncoding)
log(direction: .RECEIVE, ofType: self, datas: "WEBVIEW : \(string)")
}
//자바스크립트 메세지 핸들러
func javascriptMessageHandler(function: String, param: Dictionary<String, Any>) {
/*
Javascript 데이터의 String 값으로 메서드를 지정하기 때문에
인스턴스 메서드가 있는지 꼭 확인해야됨. 없는 메서드 호출시 오류 발생함.
*/
let selector = Selector(function)
let originalMethod = class_getInstanceMethod(JavascriptMessageHandler.self, selector)
if originalMethod == nil {
log(direction: .ERROR, ofType: self, datas: "JS->Native \(selector) : 호출할 수 없는 함수 입니다.")
return
}
javascriptMessageHandler.perform(selector, with: param)
}
//커스텀 스키마 메세지 핸들러
func schemeMessageHandler(schemeUrl: URL) {
if let scheme = schemeUrl.scheme {
switch scheme {
case "mgapp":
if let function = schemeUrl.host {
let selector = Selector(function+":")
let originalMethod = class_getInstanceMethod(CustomSchemeMessageHandler.self, selector)
if originalMethod == nil {
log(direction: .ERROR, ofType: self, datas: "CustomScheme->Native \(selector) : 호출할 수 없는 함수 입니다.")
return
}
let param = schemeUrl.queryDictionary
customSchemeMessageHandler.perform(selector, with: param)
}
default: break
}
}
}
}
ScriptMessageHandler.swift (JavaScript <-> Native 연동)
웹에서 호출하는 함수가 많을 경우가 있어 별도의 파일을 새로 만들고 JS -> Native 함수만 별도로 구현하게 분리
이부분은 개인의 성향에 맞게 구현 하면 된다.
import UIKit
import WebKit
class JavascriptMessageHandler: NSObject {
weak var delegate: MainViewControllerDelegate?
/// Native -> Javascript 전송
private func evaluateJavaScript(function: String, jsonData: [String : Any]) {
self.delegate?.webviewEvaluateJavaScript(jsFunctionString: function, param: jsonData.jsonString(), completionHandler: { result, error in
// JS Result 처리
})
}
/*
Web -> Native javascript 함수 호출 처리
참고자료 : https://life-shelter.tistory.com/315
*/
//MARK: JS -> Native Function Call
// 테스트용
@objc func login(_ params: Dictionary<String, Any>?) {
log(direction: .JAVASCRIPT, ofType: self, datas: "function : \(String(describing: params))")
if params?.count == 0 {
// 처리할 파라메터 없는 경우
return
}
if let params = params {
let jsParam = createJsonForJavaScript(for: (params))
// 테스트용 코드 받은 파라메터를 다시 자바스크립트 함수로 전송 (Native -> JS)
self.evaluateJavaScript(function: "nativeToJs", jsonData: jsParam) { result, error in
log(direction: .JAVASCRIPT, ofType: self, datas: "result : \(String(describing: result))", "error : \(String(describing: error?.localizedDescription))")
}
}
}
@objc func logout(_ params: Dictionary<String, Any>?) {
log(direction: .JAVASCRIPT, ofType: self, datas: "function : \(String(describing: params))")
}
}
SchemeMessageHandler.swift (Custom Scheme 연동)
웹에서 호출하는 스키마가 많을 경우가 있어 별도의 파일을 만들어 별도로 구현
import UIKit
import WebKit
class CustomSchemeMessageHandler: NSObject {
weak var delegate: MainViewControllerDelegate?
//MARK: Custom SCheme -> Native Function Call
@objc func externalBrowser(_ params: Dictionary<String, Any>?) {
log(direction: .SCHEME, ofType: self, datas: "Params : \(String(describing: params))")
// if params?.count == 0 {
// // 처리할 파라메터 없는 경우
// return
// }
if let jsParam = params?.jsonString() {
self.delegate?.webviewEvaluateJavaScript(jsFunctionString: "webLog", param: jsParam, completionHandler: { result, error in
log(direction: .JAVASCRIPT, ofType: self, datas: "result : \(String(describing: result))", "error : \(String(describing: error.debugDescription))")
})
}
}
}
test.html
<!--<html lang="ko">-->
<!doctype html>
<head>
<!--<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">-->
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style type='text/css'>
html { font-family:Helvetica; color:#222; }
h1 { color:#FE8FBD; font-size:24px; margin-top:24px; }
button { margin:0 3px 10px; font-size:14px; border: 2px solid #000000; }
.logLine_Native { border-bottom:1px solid #FFA67C; padding:4px 2px; font-family:courier; font-size:12px; }
.logLine_JS { border-bottom:1px solid #D867D8; padding:4px 2px; font-family:courier; font-size:12px; }
</style>
</head>
<body>
<script>
window.onerror = function(err) {
log('window.onerror: ' + err)
}
var uniqueId = 1
function log(message, data, type) {
var log = document.getElementById('log')
var el = document.createElement('div')
el.className = type == 'native' ? 'logLine_Native' : 'logLine_JS'
el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
if (log.children.length) { log.insertBefore(el, log.children[0]) }
else { log.appendChild(el) }
}
////////////////////////////////////////////////
// JS -> Native 테스트
function jsToNative1() {
// 함수명 파라메터
// 파라메터 있는 경우
var jsonObj = {"login" : {"idx":1, "name":"twok"}};
window.webkit.messageHandlers.javascript.postMessage(JSON.stringify(jsonObj));
}
function jsToNative2() {
// 함수명 파라메터
// 파라메터 없는 경우
var jsonObj = {"logout" : {}};
window.webkit.messageHandlers.javascript.postMessage(JSON.stringify(jsonObj));
}
// Native -> JS 테스트
function nativeToJs(message) {
log('JS Call', message)
return "OK"
}
// 커스텀 스키마 테스트
function schemeTest() { // 커스텀 스키마 호출
window.webkit.messageHandlers.customScheme.postMessage("myapp://externalBrowser?url=https://naver.com&target=")
}
</script>
<div id='buttons'></div>
<div id='log'></div>
<h2> <a href="javascript:jsToNative1();">JS -> Native(Params)</a> </h2> <br />
<h2> <a href="javascript:jsToNative2();">JS -> Native(Null) </a> </h2> <br />
<h2> <a href="javascript:schemeTest();">schemeTest</a> </h2> <br />
</body>
</html>
'Swift > WKWebview' 카테고리의 다른 글
[SWIFT]Custom WKWebView version 1.0 (0) | 2023.07.19 |
---|---|
[SWIFT]Javascript (JS <-> Native)/ Custom Scheme 연동 V2 (0) | 2023.06.07 |
[SWIFT]웹뷰의 보여지는 화면 스크린샷 이미지 생성 (0) | 2023.05.08 |
[SWIFT]WKWebview 탭 제스쳐 적용 (0) | 2023.04.24 |
[SWIFT]WKWebView 사용방법 (blob, data download) (0) | 2023.04.17 |