본문 바로가기

iOS/SwiftUI

[SwiftUI] UIViewRepresentable을 이용한 WKWebView 사용하기

SwiftUI로 앱을 개발할 때, UIKit 코드를 사용해야만 하는 경우가 있습니다.

SwiftUIUIKit를 혼용하기 위해 세가지 방법들(UIViewRepresentable, UIViewControllerRepresentable, UIHostingController)이 제공됩니다.

 

UIViewRepresentableUIKit ViewSwiftUI View에 통합시켜주기 위해 사용하는 프로토콜입니다.

다른 방법들은 나중에 정리하고, 이번에는 WKWebView(본인인증 연동)를 사용하기 위해 UIViewRepresentable을 사용해보겠습니다.

 

UIViewRepresentable 프로토콜을 채택하면 다음 두 가지 메소드를 필수적으로 구현해야합니다.

 

  • makeUIView

        → UIView를 생성하는 메소드입니다. 이 메소드가 리턴한 UIView가 SwiftUI View로 통합됩니다.

func makeUIView(context: Context) -> some UIView {
    guard let url = URL(string: self.urlToLoad) else { return WKWebView() }

    let urlRequest = URLRequest(url: url)
    let webView = WKWebView()
    webView.navigationDelegate = context.coordinator
    webView.configuration.userContentController.add(self.makeCoordinator(), name: "BRIDGE_AUTH_RESULT")
    webView.load(urlRequest)

    return webView
}
  • updateUIView

        → State의 값이 변경되면 호출되는 메소드입니다.

 

 

WKWebView의 탐색 결과에 따른 처리를 위해 WKNavigationDelegate를, 본인 인증 결과에 대한 처리를 위해 WKScriptMessageHandler를 Coordinator 패턴으로 구현하였습니다.

최종 코드는 다음과 같습니다.

import Foundation
import WebKit
import SwiftUI

struct AuthenticationWebView: UIViewRepresentable {
    var urlToLoad: String
    var onSuccess: () -> Void
    var onFail: () -> Void
    
    func receivedStringValueFromWebView(value: String) {
        if value == "0000" {
            onSuccess()
        } else {
            onFail()
        }
    }
    
    func makeUIView(context: Context) -> some UIView {
        guard let url = URL(string: self.urlToLoad) else { return WKWebView() }
        
        let urlRequest = URLRequest(url: url)
        let webView = WKWebView()
        webView.navigationDelegate = context.coordinator
        webView.configuration.userContentController.add(self.makeCoordinator(), name: "BRIDGE_AUTH_RESULT")
        webView.load(urlRequest)
        
        return webView
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        //do nothing
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: AuthenticationWebView

        init(_ uiWebView: AuthenticationWebView) {
            self.parent = uiWebView
        }
        
        func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            webView.scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
            return decisionHandler(.allow)
        }
        
        //초기 탐색 프로세스 중 에러
        func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
            print(#fileID, #function, #line, error)
        }
        
        //탐색 중 에러
        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            print(#fileID, #function, #line, error)
        }
    }
}

extension AuthenticationWebView.Coordinator: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        //웹으로부터 수신
        if message.name == "BRIDGE_AUTH_RESULT" {
            parent.receivedStringValueFromWebView(value: (message.body as? String)!)
        }
    }
}

 

처음 본인 인증 기능을 구현해야한다고 들었을 때는 괜히 어려울 것 같고 쫄았었는데...

막상 구현해보니, 본인 인증 서비스 업체로부터 제공받는 웹 페이지(JSP 사용)에서 본인인증 절차가 다 이루어졌고, 앱 쪽은 그냥 본인 인증 웹 페이지만 WKWebView로 보여주고 결과를 WKWebView로부터 받는 것만 구현하면 되었습니다.