iOS
[iOS] WKWebView 정리 Swift
thoonk:
2022. 2. 4. 21:53
반응형
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.
반응형