1. WKWebView 웹 뷰 띄우기

💡중요한 사실

about:blank

화면이 안보인다면 혹시 about:blank를 막은건 아닌지 살펴봐야 한다

string을 url로 바꾸고 request할 때
요청한 url 전에 about:blank가 로드되는데
이게 로드되지 않으면 웹뷰를 로드할 수 없다
url에 특정 파라미터를 추가한 뒤
파라미터를 포함한 경로가 아니면 모두 decisionhandler에서 cancle을 해버렸더니 화면이 보이지 않았다
이것때문에 시간이 너무 많이 흘렀다...

if urlAbsoluteString == "about:blank" {
      decisionHandler(.allow)
      return
}

// 클릭한 링크 확인하기

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

        guard let urlTarget = navigationAction.request.url else {
            decisionHandler(.cancel)
            return
        }
        
        if navigationAction.navigationType == .linkActivated {
            // 클릭한 링크 확인하기
        }
        
        decisionHandler(.allow)
        
    }

// target_blank 처리하기

    // this handles target=_blank links by opening them in the same view
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        
        guard let urlTarget = navigationAction.request.url else {
            return nil
        }
        
        // 인앱인지 새창인지 구분 값
        var isPopupView = false
        
        if navigationAction.targetFrame == nil {
				//뭐뭐...
            {
                webView.load(navigationAction.request)
                isPopupView = true
            } else {
                let safariViewController = SFSafariViewController(url: urlTarget)
                present(safariViewController, animated: true, completion: nil)
            }
        }

        if isPopupView {
            return popupView
        } else {
            return nil
        }
    }

인터넷에서 찾은 기본적인 템플릿

//
//  ViewController.swift
//  GetggulTest
//
//  Created by  Etoday on 2021/03/05.
//

import UIKit
import WebKit


class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {
    
    @IBOutlet weak var webView: WKWebView!
    
    override func loadView() {
        
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        
        view = webView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("viewDidLoad Call")
        
        let myURL = URL(string: "http://getggul.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
        
        let token = ""
        let script = "sendToken(\(token))"
        webView.evaluateJavaScript(script) { (result, error) in
            print(error)
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    //MARK: - alert 자바스크립트에서 표시해주는 기본 얼럿에 대한 별도 처리, message를 포함하는 UIAlertController 생성
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        let alertController = UIAlertController(title: "", message: message, preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "확인", style: .default, handler: { (action) in
            completionHandler()
        }))
        
        self.present(alertController, animated: true, completion: nil)
    }
    
    //MARK: - confirm
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        let alertController = UIAlertController(title: "", message: message, preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "확인", style: .default, handler: { (action) in
            completionHandler(true)
        }))
        alertController.addAction(UIAlertAction(title: "취소", style: .default, handler: { (action) in
            completionHandler(false)
        }))
        
        self.present(alertController, animated: true, completion: nil)
    }
    
    //MARK: - confirm2
//    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
//        let alertController = UIAlertController(title: "", message: prompt, preferredStyle: .alert)
//        alertController.addTextField { (textField) in
//            textField = defaultText
//        }
//        alertController.addAction(UIAlertAction(title: "확인", style: .default, handler: { (action) in
//            if let text = alertController.textFields?.first?.text {
//                completionHandler(text)
//            } else {
//                completionHandler(defaultText)
//            }
//        }))
//        alertController.addAction(UIAlertAction(title: "취소", style: .default, handler: { (action) in
//            completionHandler(nil)
//        }))
//        self.present(alertController, animated: true, completion: nil)
//    }
    
    //MARK: - href="_blank" 새 창 열기
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        //navagationAction내에 새로 요청하는 webview에 대한 정보가 들어있다
        //아래와 같은 형식으로 request를 받아 새 웹뷰를 띄우거나 기존 웹뷰에 load해 줄 수 있다
        //if let url = navigationAction.request.url {...}
        if navigationAction.targetFrame == nil {
            webView.load(navigationAction.request)
        }
        return nil
    }
    
    //MARK: 중복 리로드 방지
    func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
        webView.reload()
    }
    
}

https://developer.apple.com/documentation/webkit/wkwebview

💡주의사항
iOS 8 이후 UIWebView는 WKWebView로 대체되었다.
자바스크립트 엔진인 Nitro를 사용한다.
로컬로 저장된 파일에 대한 Ajax 요청을 지원하지 않는다. (file://URL에 대한 XHR(Ajax) 요청을 허용하지 않는다.
쿠키 허용, 고급 캐시 설정을 지원하지 않는다.
앱 종료 시 HTML5 로컬 스토리지가 지워진다.

UIWebView와 WKWebView는 어떻게 다른가?
https://zeddios.tistory.com/332

프레임워크 추가

단순히 import만해도 추가할 수 있지만
권장하는 방식은 프레임워크를 추가하는 것이라고 한다.

WKWebView

import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate {

    @IBOutlet weak var webView: WKWebView!
    
    override func viewDidLoad() {
        let url = URL(string: "https://www.google.com")
		let req = URLRequest(url: url!)
		self.webView.load(req)
    }

}

오류: OS_ACTIVITY_MODE

오류
2021-06-07 15:24:15.373158+0900 etoday[6626:546543] WF: === Starting WebFilter logging for process etoday
2021-06-07 15:24:15.373339+0900 etoday[6626:546543] WF: _userSettingsForUser : (null)
2021-06-07 15:24:15.373500+0900 etoday[6626:546543] WF: _WebFilterIsActive returning: NO
2021-06-07 15:24:15.767721+0900 etoday[6626:546543][Process] 0x121818818 - [pageProxyID=5, webPageID=6, PID=6635] WebPageProxy::didFailProvisionalLoadForFrame: frameID = 3, domain = NSURLErrorDomain, code = -1022

XCode 8 버전 부터 OS_ACTIVITY_MODE(os의 시스템관련 로그를 쭉 출력 해주는 모드)가 추가 되었습니다. 로그가 너무 많이 찍혀서 사용하지 않는 설정을 해준다.

Edit Scheme > OS_ACTIVITY_MODE = diable

오류: 웹뷰가 안보이고 하얀화면만 보인다

https일 때는 됐는데
로컬에 있는 http를 사용하려고 했을 때 오류가 발생했다

Info.plist 설정방법: 소스코드에 들어가서 설정하거나 Property List에서 설정할 수 있다.

    1. 소스코드로 설정하기
      Info.plist > Open As > Source Code
      ATS 설정을 아예 사용하지 않도록 설정하는 것이므로 모든 URL을 허용하여 편리하지만 보안에 취약한 방법이다.
    1. Property List에서 설정하기

아래와 같은 오류 메시지를 볼 수 없었지만 설정해줬다

오류
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Use HTTPS instead or add Exception Domains to your app's Info.plist.

App Transport Security(ATS)
iOS 9부터 신설된 외부 네트워크 관련된 보안 규칙
SSL 보안 프로토콜이 서버에 적용되어 있지 않은 경우 https가 아니라 http로 도메인이 시작한다면 보안 설정을 해야한다

번외) 공식 홈페이지 코드

https://developer.apple.com/documentation/webkit/wkwebview
WKWebViewConfiguration를 통해 더 세부설정을 할 수 있다

import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
    
    var webView: WKWebView!
    
    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        view = webView
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myURL = URL(string:"https://www.apple.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }
}

WebKit Delegate

    1. WKUIDelegate: 자바스크립트, 기타 플러그인 컨텐츠 이벤트를 캐치하여 동작, 웹 페이지 기본 사용자 인터페이스 요소를 제공
    1. WKNavigationDelegate: 프로토콜로 페이지의 start, loading, finishi, error의 트리거 이벤트를 캐치하여 사용자 정의 동작을 구현가능
    1. WKScriptMessageHandler: 프로토콜로 웹 페이지에서 실행되는 Javascript Message를 수신하는 방법을 제공, 웹 페이지에서 스크립트 메시지가 수신될 때 호출되는 userContentController를 정의하고 있다.

   func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if navigationAction.navigationType == .linkActivated  {
            if let url = navigationAction.request.url,
                let host = url.host, !host.hasPrefix("www.google.com"),
                UIApplication.shared.canOpenURL(url) {
                UIApplication.shared.open(url)
                print(url)
                print("Redirected to browser. No need to open it locally")
                decisionHandler(.cancel)
            } else {
                print("Open it locally")
                decisionHandler(.allow)
            }
        } else {
            print("not a user click")
            decisionHandler(.allow)
        }
    }
        let preferences = WKPreferences()
        preferences.javaScriptEnabled = true
        
        let configuration = WKWebViewConfiguration()
        configuration.websiteDataStore =  WKWebsiteDataStore.nonPersistent()
        configuration.preferences = preferences
        
        self.mWebView = WKWebView(frame: CGRect.zero, configuration: configuration)
        
        self.mWebView.translatesAutoresizingMaskIntoConstraints = false
        self.mWebView.allowsLinkPreview = true
        self.mWebView.allowsBackForwardNavigationGestures = true
        self.mWebView.navigationDelegate = self
        self.view.addSubview(self.mWebView)

에러

Error Domain=NSURLErrorDomain Code=-999 "(null)" UserInfo={NSErrorFailingURLStringKey=도메인, NSErrorFailingURLKey=도메인, _WKRecoveryAttempterErrorKey=<WKReloadFrameErrorRecoveryAttempter: 0x280f81920>}

쉬운게 없구나...😭

URL Loading System Error Codes
These values are returned as the error code property of an NSError object with the domain “NSURLErrorDomain”.

enum
{
   NSURLErrorUnknown = -1,
   NSURLErrorCancelled = -999,
   NSURLErrorBadURL = -1000,
   NSURLErrorTimedOut = -1001,

As you can see; -999 is caused by ErrorCancelled
This means: another request is made before the previous request is completed.

https://myksb1223.github.io/develop_diary/2019/02/21/NSURLSession-consideration.html

NSURLSession을 사용중에 새로운 화면에 들어가면 아래 로그를 띄우며 계속 NSURLSession이 취소되었다.

NSURLSession에서 Task가 cancel되었을 때 뜨는 코드이다.

일단 맨 처음에 생각한 것은 한번에 너무 많은 Connection을 처리하는 것이 아닌가에 대한 생각이었다.

NSURLSession은 같은 URL을 가진 녀석이 중복해서 들어올 경우에는 가장 나중에 들어온 녀석만 수행하고 자체적으로 앞의 처리를 취소한다고 한다.

즉, 자체 큐(Queue)를 가지고 있어 알아서 처리한다는 것이다. 즉, 새롭게 NSURLSession객체를 만들 필요가 없는 것이다.

결국 원인은 예전에 NSURLConnection을 사용할 때, 직접 큐를 만들어 관리했었는데, 해당 부분의 Cancel 처리부분이 NSURLSession의 모든 Task를 취소하고 있었던 것이었다…

즉, [NSURLSession sharedSession]객체는

전체 앱의 Session을 관리한다.
자체적으로 큐를 가지고 있고, 해당 큐에 들어온 Task를 처리하는데, 중복된 URL을 가진 Task가 들어오면 먼저 들어온 앞서 들어온 Task를 취소한다.
HTTPMaximumConnectionsPerHost는 한 호스트에 동시에 연결할 수 있는 숫자를 나타낸다. 즉, 같은 Host에 10개의 Task를 요청하면 4개까지 동시에 연결되고 나머지 6개는 큐에서 기다린다.

좋은 웹페이지 즐겨찾기