본문 바로가기
iOS_Swift 앱개발👍

[iOS_Swift] WKWebView. 이 글 하나로 정리 끝. _ 31

by 개발하는윤기사 2023. 7. 25.
728x90
반응형

 

 

WKWebView는 하나의 클래스로, 앱 안에 웹 페이지, 즉 웹 콘텐츠를 View 형식으로 보여주는 걸 의미합니다!

 

WKWebView의 가장 큰 장점은 웹 페이지 메모리가 아무리 크더라도, 웹 페이지에서 할당하는 메모리는 앱과 별도의 스레드에서 관리하기 때문에 문제없습니다.

 

가장 많이 사용되는 WebKit을 이용한 WebView 사용법에 대해 정리하고자 합니다.

 

이 글 하나로 모든 걸 정리하겠습니다! 시작!!

 

 

WebViewController

1. import WebKit

2. WKWebView를 담아줄 콘텐츠 뷰가 필요하기 때문에 UIView를 이용해 웹뷰 배경 뷰를 생성해 줍니다.

import UIKit
import WebKit

class WebViewController: UIViewController {

    @IBOutlet weak var webViewBackgroundView: UIView!/** 웹뷰 배경 뷰 */
    
    private var webView: WKWebView!/** 웹 뷰 */
    
    override func viewDidLoad() {
        super.viewDidLoad()
	
	    webViewBackgroundView.addSubview(webView)

	    webViewConfig()
        
	}
    
}

 

WebView Configuration

- 웹 뷰를 사용하기 위해선 웹 뷰 기본설정이 필요합니다.

- WKPreferences : javaScript와 interaction 하기 위한 기본 설정

- WKUserContentController : javaScript의 messageHandler를 찾는 역할

- WKWebViewConfiguration : WKPreferences, WKUserContentController 등록.

private func webViewConfig() {
        
    /* javaScript 사용 설정 */
    let preferences = WKWebpagePreferences()
    preferences.allowsContentJavaScript = true
    
    /* 자동으로 javaScript를 통해 새 창 열기 설정 */
    let wkPreferences = WKPreferences()
    wkPreferences.javaScriptCanOpenWindowsAutomatically = true
    
    /* contentController 설정 */ 
    let contentController = WKUserContentController()
    contentController.add(self, name: "somethingToDo")
    
    let configuration = WKWebViewConfiguration()
    configuration.preferences = wkPreferences
    configuration.defaultWebpagePreferences = preferences
    configuration.userContentController = contentController
    
    webView = WKWebView(frame: self.view.bounds, configuration: configuration)
    
    webView.navigationDelegate = self
	webView.uiDelegate = self
    
}

 

위에 선언한 webView에 부가적인 기능을 설정할 수 있다.

 

- scrollView.bounces : 수직 스크롤을 내렸을 때 bounce 유무

- scrollView.showVerticalScrollIndicator : 수직 스크롤바를 표시할 건지 유무

- allowsLinkPreview : 해당 링크의 Preview 유무

- allowsBackForwardNavigationGestures : true이면 '뒤로 가기' 제스쳐가 동작하는 것이고, false면 동작하지 않는다. false 처리를 하고 싶다면 뒤로가기 버튼이나 종료 버튼을 만들어주어야 한다.

webView.scrollView.bounces = false
webView.scrollView.showsVerticalScrollIndicator = false
webView.allowsLinkPreview = false
webView.allowsBackForwardNavigationGestures = false

 

원하는 URL 띄우기

- URLComponents 

- URL

- html url을 이용해 직접 로드하는 법

- html 파일을 직접 추가해서 로드하는 법

//1. URLComponents를 활용하는 법
let googleComponents = URLComponents(string: "<https://www.google.com/>")!
let googleRequest = URLRequest(url: googleComponents.url!)
webView.load(googleRequest)

//2. URL을 이용해서 load하는 방법
let myURL = URL(string: urlString)
let myRequest = URLRequest(url: myURL!)
DispatchQueue.main.async {
	self.webView.load(myRequest)
}

//3. login.html 파일을 직접 로드하는 법
let myURL = Bundle.main.url(forResource: "login", withExtension: "html", subdirectory: "html")!
DispatchQueue.main.async {
    self.webView.loadFileURL(myURL, allowingReadAccessTo: myURL)
}

//4. index.html을 직접 추가해서 띄우는 법
guard let path = Bundle.main.path(forResource: "index", ofType: "html") else { return }
let url = URL(fileURLWithPath: path)
let urlRequest = URLRequest(url: url)
webView.load(urlRequest)

 

JavaScript에 접근하는 법

1) WKUserScript

  • WKUserScript를 이용해 javascript의 function getName() 함수에 접근 가능!
let userScript = WKUserScript(source: "getName()", injectionTime: .atDocumentEnd, forMainFrameOnly: true)
webView.configuration.userContentController.addUserScript(userScript)

2) evaluateJavaScript (iOS(Native) -> Web)

- JSONSerialization : JSON을 배열 혹은 딕셔너리 형태로, 반대로 배열 혹은 딕셔너리 형태를 JSON 타입으로 바꿔주는 객체

- 사용하기 위해 딕셔너리 형태로 온 파라미터 값을 JSON 형태로 변환해서 Web에 전달해 주는 역할이라고 보면 됩니다.

let Params:[String:Any] = ["type" : type,
                           "data" : data,
                           "params" : params,
                           "return" : return]

let dictionary = Params as NSDictionary
let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted)
let jsonString = NSString(data: jsonData!, encoding: String.Encoding.utf8.rawValue)! as String

DispatchQueue.main.async {
    self.webViewChildren.forEach({ (key, value) in
        value.evaluateJavaScript("requestJS:requestJS(\(jsonString))") { (result, error) in
            print(result)
            completion(result)
        }
    })
}

 

3. WKScriptMessageHandler (Web -> iOS(Native))

  • didReceive : javascript의 messageHandler 값을 구별해서 처리해 주는 메서드.
    • message의 name과 body가 존재.
    • message의 name과 body 값을 이용해서 분기처리를 해서 원하는 동작을 실행시킬 수 있다.
func userContentController(_ userContentController: WKUserContentController,
                           didReceive message: WKScriptMessage) {

//message.name과 message.body를 이용    
if(message.name == "somethingToDo") {

    let body = message.body as? NSDictionary
    let type = body?.value(forKey: "type") as? String

    if (type == "custom") {
        // 특정 VC 이동
        let pushVC = self.storyboard?.instantiateViewController(withIdentifier: "CustomViewController") as! CustomViewController
        self.navigationController?.pushViewController(pushVC, animated: true)

    } else if (type == "review") {
            // 리뷰 화면 표출
    } else if (type == "buy") {
            // 구매하기
    } else if (type == "restore") {
            // 구매 내역 복원하기
    }
}

 

WKUIDelegate

- Alert, Confirm, createWebViewWith 등 UI 관련된 메서드 설정 가능

//Alert 설정을 할 수 있음. Native Alert 띄우기
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {

    print(#function)
    let alertController = UIAlertController(title: "🌈Alert 제목🌈", message: message, preferredStyle: .alert)
    let cancelAction = UIAlertAction(title: "확인", style: .cancel) { _ in
        completionHandler()
    }
    alertController.addAction(cancelAction)
    self.present(alertController, animated: true, completion: nil)
}

//Confirm
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
			
  print(#function)
  let alertController = UIAlertController(title: "🌈Confirm 제목🌈", message: message, preferredStyle: .alert)
  let cancelAction = UIAlertAction(title: "취소", style: .cancel) { _ in
      completionHandler(false)
  }
  let okAction = UIAlertAction(title: "확인", style: .default) { _ in
      completionHandler(true)
  }
  alertController.addAction(cancelAction)
  alertController.addAction(okAction)
  self.present(alertController, animated: true, completion: nil)

}

//새로운 윈도우 창 열기
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {

	  let naverUrl = URL(string: "www.naver.com")
      let requestUrl = navigationAction.request.url
      guard naverUrl?.host == requestUrl?.host else {
          return nil
      }

      let newWebview = createWebView(configuration)
      newWebview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
      view.addSubview(newWebview)

      webViewChildren[newWebview.hashValue] = newWebview

      return newWebview

}

 

WKNavigationDelegate

기본적으로 웹 뷰를 이용해서 웹 페이지에 진입할 때의 아래와 같은 생명주기를 따른다.

 

1. decidePolicyFor navigationAction : 탐색 요청 허용 및 거부에 대한 설정, 특정 도메인을 제외한 도메인은 연결 거부 시킬 수 있다.

2. didStartProvisionalNavigation : 탐색이 시작되었음을 알려줌.

3. decidePolicyFor navigationResponse : 새로운 웹 콘텐츠 탐색 권한을 요청

4. didCommit : 웹 콘텐츠를 수신하기 시작

5. didFinish : 탐색 완료.

//1번째 실행
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
     
    //탐색 요청 허용 및 거부
    //지정된 작업 정보를 기반으로 새 콘텐츠를 탐색할 수 있는 권한을 대리인에게 요청하는 func
    if navigationAction.request.url?.absoluteString == "about:blank" {
        decisionHandler(.allow)
        return
    }

    // 이런식으로 특정 도메인을 제외한 도메인을 연결하지 못하게 할 수 있다. 
    if let host = navigationAction.request.url?.host {
            if host != "swiftyun.tistory.com" {
                decisionHandler(.cancel)
                return
            }

    decisionHandler(.allow)
		
}

//3번째 실행 : 탐색 요청에 대한 응답이 알려진 후 대리인에게 새 콘텐츠 탐색 권한을 요청
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    decisionHandler(.allow)
    return
}
//2번째 실행
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    print("주 프레임에서 탐색이 시작되었음을 대리자에게 알림")
}

func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
    print("웹 보기가 요청에 대한 서버 리디렉션을 수신했음을 대리자에게 알림")
}

func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    print("대리자에게 인증 질문에 응답하도록 요청")
}

func webView(_ webView: WKWebView, authenticationChallenge challenge: URLAuthenticationChallenge, shouldAllowDeprecatedTLS decisionHandler: @escaping (Bool) -> Void) {
    print("사용되지 않는 버전의 TLS를 사용하는 연결을 계속할지 여부를 대리자에게 물음")
}

//4번째 실행
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
    print("웹 보기가 메인 프레임에 대한 콘텐츠를 수신하기 시작했음을 대리자에게 알림")
}

//5번째 실행
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    print("탐색이 완료되었음을 대리자에게 알림")
}

//오류 발생
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
    print("탐색 중 오류가 발생했음을 대리자에게 알림")
}

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    print("초기 탐색 프로세스 중에 오류가 발생했음을 대리자에게 알림")
}

func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
    print("웹 보기의 콘텐츠 프로세스가 종료되었음을 대리자에게 알림")
}

 

🌸 WKWebView의 생명주기를 이용한 LoadingBar 띄워주기

  • LoadingBar를 표현해주고 싶다면, decidePolicyFor navigationAction에 로딩바를 show 해주고, didFinish가 호출 시점에 dismiss 해주면 된다.

728x90
반응형