본문 바로가기
iOS

[iOS] WKWebView 정리 Swift

by thoonk: 2022. 2. 4.

WKWebView를 사용하면서 설정했던 것들을 기록합니다.

 

WKWebView 특징

  • 기존의 UIWebview를 대신하여 사용하며 iOS 9 이상의 버전을 필요로 합니다.  
  • 또한, 자바스크립트 엔진인 Nitro를 사용해서 UIWebView보다 성능이 더 좋습니다.
  • 로컬로 저장된 파일에 대한 Ajax 요청을 지원하지 않습니다. 
  • 쿠키 허용 설정 및 고급 캐시 설정 지원을 하지 않고 앱 종료 시 HTML5 로컬 스토리지가 삭제됩니다.

 

기본 설정

import UIKit
import WebKit
class ViewController: UIViewController {
    var webView: WKWebView!
    
    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myURL = URL(string:"https://www.apple.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
		}
}

 

네이티브(WKWebView) -> 웹 송신

webView.evaluateJavaScript("method('\(data)')") { posts, err in
    if let err = err {
        debugPrint("Error: postPushInfo - \(err)")
    } else {
        debugPrint("Posts: postPushInfo - \(posts ?? "No Posts")")
    }
}

 

웹 -> 네이티브(WKWebView) 수신

WKScriptMessageHandler

  • JavaScript 메시지를 수신합니다.

 

let userController = WKUserContentController()
userController.add(self, name: "method")

let config = WKWebViewConfiguration()
config.userContentController = userController

webView = WKWebView(frame: .zero, configuration: config)
  • WKUserContentController 클래스를 사용해서 JavaScript 함수 추가해야 합니다.

 

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
		switch message.name {
		case "method":
				if let data = self.extractJSONObjFromScriptMsg(with: message) {
		        // Do something to process.
        }
		}
}
  • didReceive 함수를 통해서 웹으로부터 수신한 데이터를 JavaScript 함수 명으로 분기처리합니다.

 

private func extractJSONObjFromScriptMsg(with message: WKScriptMessage) -> [String: Any]? {
    if let objStr = message.body as? String {
        let data: Data = objStr.data(using: .utf8)!
        do {
            let jsonObj = try JSONSerialization.jsonObject(with: data, options: .init(rawValue: 0))
            if let jsonObjDic = jsonObj as? [String: Any] {
                return jsonObjDic
            }
        } catch {
            print("Error: JSONSerialization Data")
        }
    }
    return nil
}
  • 위 코드의 extractJSONObjFromScriptMsg 함수는 데이터가 JSON형태로 왔을 때 파싱하는 기능을 합니다.

WKUIDelegate

  • JavaScript, 기타 플러그인 컨텐츠 이벤트 캐치하여 동작합니다.
  • 웹 페이지 기본 사용자 인터페이스 요소를 제공합니다.
  • 웹 페이지 대신 네이티브 사용자 인터페이스 요소를 표시할 때 사용합니다.
  • 딜리게이트 함수를 이용하여 서버에서 호출하는 alert함수와 Confirm 등을 호출할 때 WebView 안에서 수신을 하게되면 상단 부분 Web 주소가 노출되는 문제 → 딜리게이트 함수 이용하여 원하는 화면으로 구성하여 출력 가능

 

func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
		let ac = UIAlertController(title: "알림", message: message, preferredStyle: .alert)
    let okAction = UIAlertAction(title: "확인", style: .default, handler: handler)

    ac.addAction(okAction)
    self.present(ac, animated: true)
}
  • JavaScript Alert 함수 호출 시 호출되는 메서드입니다.

 

func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        
    let ac = UIAlertController(title: "알림", message: message, preferredStyle: .alert)
    let cancelAction = UIAlertAction(title: "취소", style: .cancel) { _ in
        completionHandler(false)
    }
    let okAction = UIAlertAction(title: "확인", style: .default) { _ in
        completionHandler(true)
    }
        
    ac.addAction(cancelAction)
    ac.addAction(okAction)
        
    self.present(ac, animated: true)
}
  • JavaScript Confirm 함수 호출 시 호출되는 메서드입니다.

 

func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
    
    let ac = UIAlertController(title: "알림", message: "내용", preferredStyle: .alert)
    ac.addTextField { textField in
        textField.text = defaultText
    }
    
    let okAction = UIAlertAction(title: "확인", style: .default) { _ in
        if let inputText = ac.textFields?.first?.text {
            completionHandler(inputText)
        } else {
            completionHandler(defaultText)
        }
    }
    
    let cancelAction = UIAlertAction(title: "취소", style: .cancel) { _ in
        completionHandler(nil)
    }
    
    ac.addAction(cancelAction)
    ac.addAction(okAction)
    
    self.present(ac, animated: true)
}
  • JavaScript Prompt 함수 호출 시 호출되는 메서드입니다.

WKNavigationDelegate

  • 웹 페이지의 start, loading, finish, error 이벤트 캐치하여 사용자 정의 동작 구현 기능입니다.
  • 웹 뷰에서 탐색 요청을 수락, 로드 및 완료하는 과정에서 트리거되는 사용자 지정 동작을 구현하는데 사용합니다.
  • 딜리게이트 함수를 이용하여 로딩 아이콘 표시 여부 및 URL, Protocol에 따른 다른 작업을 처리해 주는 용도로 주로 사용합니다.

 

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {        
		if let host = navigationAction.request.url?.host, 
				if host == "www.apple.com" {
					decisionHandler(.allow)
					return
				}
		} 
		decisionHandler(.cancel)
}
  • decidePolicyFor : 해당 웹 페이지를 로드할지 말지 결정
  • decisionHandler()
    • .allow → 해당 웹 페이지 로드
    • .cancel → 해당 웹 페이지 로드하지 않음

 

func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    <#code#>
}
  • 웹뷰가 컨텐츠를 로드할 때 호출

 

func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
    <#code#>
}
  • 웹뷰가 콘텐츠를 받기 시작할 때 호출
  • URL이 유효하지 않을 때 호출되지 않음

 

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    <#code#>
}
  • 웹뷰가 컨텐츠를 다 로드했을 때 호출

 

func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
    <#code#>
}
  • 웹뷰가 콘텐츠 로드 실패했을 때 호출 

 

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
		<#code#>
}
  • 네트워크에 문제가 있거나 URL이 잘못 되었을 때 호출
  • 위의 이유로 didFail 메서드 대신 호출되기 때문에 didFail 메서드와 동일하게 처리해주는 것이 좋음

UserAgent 설정

webView.evaluateJavaScript("navigator.userAgent") { [weak self] result, err in
    guard let self = self,
          let originAgent = result as? String
    else {
        self?.setupAlertView(with: "Error: Get UserAgent")
        return
    }
    
    let agent = originAgent + "Custom Agent"
    webView.customUserAgent = agent
}

 


쿠키 설정

extension WKWebViewConfiguration {
    /// 웹뷰 쿠키 설정
    static func cookiesIncluded(completion: @escaping (WKWebViewConfiguration?) -> Void) {
        let config = WKWebViewConfiguration()
        guard let cookies = HTTPCookieStorage.shared.cookies else {
            completion(config)
            return
        }
        
        let dataStore = WKWebsiteDataStore.default()
        // 저장 유무 nonPersistent() or default() 
        
        let waitGroup = DispatchGroup()
        for cookie in cookies {
            waitGroup.enter()
            dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
        }
        waitGroup.notify(queue: DispatchQueue.main) {
            config.websiteDataStore = dataStore
            completion(config)
        }
    }
}

네트워크 설정 

import SystemConfiguration

public class Reachability {
    class func isConnectedToNetwork() -> Bool {

        var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
        zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
        zeroAddress.sin_family = sa_family_t(AF_INET)

        let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
                SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
            }
        }
        var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
        if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
            return false
        }

        /* Only Working for WIFI
        let isReachable = flags == .reachable
        let needsConnection = flags == .connectionRequired

        return isReachable && !needsConnection
        */

        // Working for Cellular and WIFI
        let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
        let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
        let ret = (isReachable && !needsConnection)

        return ret
    }
}

 

Usage 

private func checkNetworkAvailable() {
    if Reachability.isConnectedToNetwork() == true {
        self.load()
    } else {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            // alertView 표시
        }
    }
}

WKWebView Embedded Video Player 설정

let config = WKWebViewConfiguration()
// 전체화면이 아니더라도 플레이 가능
config.allowsInlineMediaPlayback = true
// PIP Mode On/Off
config.allowsPictureInPictureMediaPlayback = false

 

부족한 점은 피드백해주시면 감사합니다. 

 

Ref.

'iOS' 카테고리의 다른 글

[iOS] Custom Navigation Controller Pop Gesture Swift  (0) 2022.09.22
[iOS] 버튼 이미지가 표시되지 않는 이슈  (0) 2022.07.11
[iOS] Unwind Segue  (1) 2021.11.11
[iOS] CaseIterable  (1) 2021.06.27
[iOS] Outlets은 왜 옵셔널일까?  (0) 2021.06.18

댓글