🧑‍💻/Swift

Swift WKWebview 구현하기

유리맥 2023. 8. 5. 19:32
반응형

앱에 WebView를 띄워 postMessage를 전달하고 결과를 받아 봅시다.


WKWebview 생성하기

먼저 WebKit을 import 하고 WKWebView 타입 변수를 생성하고 View에 WKWebView를 추가합니다.

import UIKit
import WebKit

class WebViewController: UIViewController, WKUIDelegate {
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        createWebView()
    }
    
    /// WKWebView 생성
    func createWebView() {
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        webView.frame = view.bounds
        view.addSubview(webView)
    }
}

WKUIDelegate를 채택하여 WebView의 생성/삭제, JavaScript의 이벤트 수신할 수 있습니다.
https://developer.apple.com/documentation/webkit/wkuidelegate

 

WKUIDelegate | Apple Developer Documentation

The methods for presenting native user interface elements on behalf of a webpage.

developer.apple.com

 

 

WKWebview 불러오기

WKWebView의 load API를 호출하면 웹페이지를 불러옵니다.

/// WebView 불러오기
func loadWebView() {
    guard let url = URL(string: "https://주소를 입력하세요") else { return }
    let request = URLRequest(url: url)

    webView.load(request)
}

 

 

PostMessage 보내기

window.postMessage()와 동일한 역할을 하는 API 이름을 알기 쉽지 않았는데
구글링도 아니고 삽질을 통해 알아냈습니다..ㅎㅎ
WKUserScript를 생성하여 Configuration에 추가해 줍니다.

/// WKWebView 생성
func createWebView() {
    let webConfiguration = WKWebViewConfiguration()
    
    ////////////////////////////////////
    // postMessage 설정
    let requestData: String = "전송할 데이터"
    let userScript = WKUserScript(source: "postMessage('\(requestData)')",
                           injectionTime: .atDocumentEnd,
                        forMainFrameOnly: true)
    webConfiguration.userContentController.addUserScript(userScript)
    webConfiguration.preferences.javaScriptEnabled = true
    ////////////////////////////////////
    
    webView = WKWebView(frame: .zero, configuration: webConfiguration)
    webView.uiDelegate = self
    view.addSubview(webView)
}

 

 

메시지 수신하기

메시지를 수신하기 위해 WKScriptMessageHandler를 채택하고, userContentController에 응답이름을 등록합니다.
WKScriptMessageHandler는 userContentController(_:didReceive:) 함수를 필수로 구현해야 합니다.
message.body로 수신한 메시지를 확인해 봅시다.

class WebViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler {
    var webView: WKWebView!
    let responseName: String = "응답이름"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        createWebView()
    }

    /// WKWebView 생성
    func createWebView() {
        let webConfiguration = WKWebViewConfiguration()

        // postMessage 설정
        let requestData: String = "전송할 데이터"
        let userScript = WKUserScript(source: "postMessage('\(requestData)')",
                               injectionTime: .atDocumentEnd,
                            forMainFrameOnly: true)
        webConfiguration.userContentController.addUserScript(userScript)
        webConfiguration.preferences.javaScriptEnabled = true

        ////////////////////////////////////
        // 메시지 수신할 핸들러 등록
        webConfiguration.userContentController.add(self, name: responseName)
        ////////////////////////////////////

        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        view.addSubview(webView)
    }
    
    /// WebView 메시지 핸들러
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard message.name == responseName, let body = message.body as? String else { return }
        // body를 파싱하거나 결과 메시지를 확인합니다.
        NSLog("수신한 메시지: \(body)")
    }
}

 

 

WebView에서 카메라 사용 시 카메라 권한 확인하기

Configuration에 allowsInlineMediaPlayback을 true로 설정합니다.
카메라 권한이 있을 경우 이전에 구현한 loadWebView를 호출하도록 했습니다.
그런데 카메라 권한을 앱에서 줘도 WebView에서 한 번 더 체크하더라구요? 제거하는 방법은 찾지 못했습니다.. 어쩔 수 없는 것인지..

import AVFoundation

/// WKWebView 생성
func createWebView() {
    let webConfiguration = WKWebViewConfiguration()
    
    ////////////////////////////////////
    // 카메라 권한 허용
    webConfiguration.allowsInlineMediaPlayback = true
    ////////////////////////////////////

    // postMessage 설정
    let requestData: String = "전송할 데이터"
    let userScript = WKUserScript(source: "postMessage('\(requestData)')",
                           injectionTime: .atDocumentEnd,
                        forMainFrameOnly: true)
    webConfiguration.userContentController.addUserScript(userScript)
    webConfiguration.preferences.javaScriptEnabled = true

    // 메시지 수신할 핸들러 등록
    webConfiguration.userContentController.add(self, name: responseName)

    webView = WKWebView(frame: .zero, configuration: webConfiguration)
    webView.uiDelegate = self
    view.addSubview(webView)
}

/// Camera 권한 체크
func checkCameraPermission() {
    let permission = AVCaptureDevice.authorizationStatus(for: .video)
    switch permission {
    case .authorized:
        DispatchQueue.main.async { self.loadWebView() }
    case .denied:
        let alert = UIAlertController(title: "카메라 권한 필요",
                                      message: "설정 > 개인 정보 보호 > 카메라에서 권한을 변경하실 수 있습니다.",
                                      preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(okAction)
        present(alert, animated: false, completion: nil)
        return
    case .notDetermined:
        // 권한 요청
        AVCaptureDevice.requestAccess(for: .video) { granted in
            if granted {
                DispatchQueue.main.async { self.loadWebView() }
            } else {
                NSLog("권한이 거부되었습니다.")
            }
        }
    default:
        NSLog("Permission = \(permission.rawValue)")
    }
}

카메라 권한을 얻기 위해서는 Info.plist에 Privacy - Camera Usage Description을 설정해야 합니다.
안할 경우 crash 발생합니다.

 

 

JSON Encoding/Decoding

/// JSON Encoding
func encodedString() -> String? {
    var jsonData: [String: Any] = []
    
    do {
        // JSON
        let jsonData = try JSONSerialization.data(withJSONObject: jsonData, options: .prettyPrinted)
        return encodedString = String(data: jsonData, encoding: .utf8)
    } catch {
        NSLog(error.localizedDescription)
    }
    return nil
}

/// JSON Decoding
func parsingJson(_ jsonString: String) -> MyResponse? {
    if let jsonData = jsonString.data(using: .utf8) {
        let decoder = JSONDecoder()
        let response = try? decoder.decode(MyResponse.self, from: jsonData)
        return response
    }
    return nil
}

struct MyResponse: Codable {
    let result: String
}
반응형