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 해주면 된다.
'iOS_Swift 앱개발👍' 카테고리의 다른 글
[iOS_Swift] APNs 원격 푸시 알림 _ 33 (0) | 2023.07.31 |
---|---|
[iOS_Swift] 로컬 푸시 알림 _ 32 (0) | 2023.07.27 |
[iOS_Swift] NotificationCenter를 이용한 VC간 값 전달 _ 30 (0) | 2023.01.17 |
[iOS_Swift] Delegate Pattern을 이용한 값 전달 _ 29 (0) | 2023.01.14 |
[iOS_Swift] RxSwift를 이용한 로그인 화면 만들기 _ 28 (3) | 2022.11.06 |