앱을 다시 시작하면 WKWebView 쿠키가 모두 사라져요.

WKWebView에서 생성된 쿠키가 앱을 재시작할때 모두 사라지는 문제는 여러 해결 방법이 있지만, 그 중 가장 단순한 건 역시 모든 통신 종료 시점에 쿠키를 저장했다가 앱을 시작하면 쿠키를 수동으로 생성 시켜 주는 방법일 것입니다.

이전에도 Javascript를 애용했으므로 이번에도 Javascript를 이용하도록 하겠습니다.

예제 상에서 파일을 저장하고 불러 오는 것은 Devg.framework를 이용하도록 하겠습니다.
(https://bitbucket.org/ZenR/devg.git)

우선 모든 웹 통신이 종료될 시 가지고 있던 쿠키를 평문으로 저장하도록 하겠습니다. 보안에 좀 더 신경을 쓴다면 한번 암호화를 거치는 것도 좋겠죠.

func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation) {
    webView.evaluateJavaScript("document.cookie") { (object, error) in // 자바스크립트로 모든 쿠키를 가져와서
         if let string: String = object as? String { // 스트링으로 변환 후 저장.
             Devg.createFile("COOKIE", contents: string) // 파일 저장하는 부분이므로 대체 하세요.
         }
    }
}

 

 

이제 저장한 쿠키 정보를 불러와서 웹뷰에서 생성하는 작업을 해주어야 합니다.
적절한 도메인에 쿠키를 생성해 주기 위해 첫 웹뷰 로딩은 그냥 진행 시키도록 할게요.
결국 Javascript 기반이기 때문에 쿠키 생성도 didFinishNavigation 에서 진행해 주어야 하는데 쿠키 파일 저장과 생성하는 것이 한 곳에서 이루어 지므로 구분 하기 위해 isFirstLoad라는 멤버 변수를 만들어 주겠습니다.

var isFirstLoad: Bool = true

이제 isFirstLoad 변수를 참조하여 didFinishNavigation에 쿠키 생성하는 코드를 추가 합니다.

func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation) {
    if self.isFirstLoad { // 앱 처음 실행인지 판단.
        self.isFirstLoad = false // 쿠키 생성을 다시 하지 않도록 처리.
        if let string: String = Devg.getContents("COOKIE") { // 저장된 쿠키 파일을 불러 옴.
            // 쿠키를 생성하는 Javascript
            let cookies: [String] = string.componentsSeparatedByString(" ")
            var javascript: String = ""
            for cookie: String in cookies {
                javascript = javascript + "document.cookie='\(cookie)';"
            }
            webView.evaluateJavaScript(javascript, completionHandler: { (object, error) in // 쿠키를 생성하는 Javascript를 실행.
                webView.reload() // 쿠기 생성 후 쿠키 정보가 반영된 웹페이지를 불러 올 수 있도록 새고 고침을 해줍니다.
            })
        }
    }
    else {
        webView.evaluateJavaScript("document.cookie") { (object, error) in // 자바스크립트로 모든 쿠키를 가져와서
             if let string: String = object as? String { // 스트링으로 변환 후 저장.
                 Devg.createFile("COOKIE", contents: string) // 파일 저장하는 부분이므로 대체 하세요.
             }
        }
    }
}

https://ip.devg.net 은 사용자 아이피, 헤더 정보 등을 확인할 수 있는 웹페이지인데, 쿠키 정보도 볼 수 있으므로 간단히 테스트 후 실제 URL을 적용하시면 됩니다.

Advertisements

이름으로 쿠키 가져오기 in Javascript

function getCookie(name) {
  var value = "; " + document.cookie;
  var parts = value.split("; " + name + "=");
  if (parts.length == 2) return parts.pop().split(";").shift();
}

Swift에서도 쿠키를 가져 올 수 있으나 가급적 웹뷰에 대한 부분은 Javascript에 의존하기로 합시다.

위 Javascript를 아래와 같이 적용하면 필요한 쿠키를 손쉽게 가져올 수 있습니다.

webView.evaluateJavaScript("function getCookie(name) {  var value = \"; \" + document.cookie;  var parts = value.split(\"; \" + name + \"=\");  if (parts.length == 2) return parts.pop().split(\";\").shift();}; getCookie('basket_cnt');") { (object, error) in
    print(object)
}

 

WKWebView 에서 Ajax 통신을 감지하는 방법. feat. Javascript

UIWebView 시절에도 그랬지만, WKWebView 에서도 Ajax 통신을 감지할 수 없습니다.

WKWebView 에서 추가된

@available(iOS 8.0, *)
optional public func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void)

에서는 <iframe /> 통신(완료)은 감지 가능하지만 역시 Ajax 통신을 감지할 수 없습니다.

웹은 Swift의 전문 분야가 아닙니다. 웹의 전문 분야인 Javascript에게 도움을 요청합시다.

@available(iOS 8.0, *)
optional public func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!)

didFinishNavigation에서 일반 통신이 종료되는 시점을 감지하여 Ajax 통신을 감지하는 자바스크립트 명령을 삽입합니다.

self.webKit.evaluateJavaScript("_send = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { alert('ajax:'); var callback = this.onreadystatechange; this.onreadystatechange = function() { if (this.readyState === 4 &amp;amp;&amp;amp; this.status === 200) { alert('ajaxDone'); } callback.apply(this, arguments); }; _send.apply(this, arguments); }; 'script inserted';", completionHandler: { (object, error) in
    print(object)
})

위 명령은 Ajax 를 관장하는 XMLHttpRequest의 send() 메소드를 오버라이딩하여 Ajax 통신 상태를 앱에 전달하여 주기 위한 자바스크립트입니다.

_send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
    alert('ajax');
    var callback = this.onreadystatechange;
    this.onreadystatechange = function() {
        if (this.readyState === 4 && this.status === 200) {
            alert('ajaxDone');
        }

        callback.apply(this, arguments);
    };

    _send.apply(this, arguments);
};

Javascript와 WKWebView간의 효과적인 통신 방법은 WKUserContentController를 이용하는 방법이 있습니다. 하지만 이것은 다음에 기회가 되면 다시 설명 드리기로 하고 여기에서는 간단하게 Javascript의 alert()과 WKUIDelegate의

@available(iOS 8.0, *)
optional public func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void)

를 이용하는 방법을 사용하겠습니다. 위 protocol은 WKWebView에서 alert() 자바스크립트가 작동되면 호출됩니다.

위 자바스크립트를 이용하여 Ajax(XMLHttpRequest)가 작동하면 alert(‘ajax’); 를 호출 하도록 했고, 통신이 정상적으로 완료 되면 alert(‘ajaxDone’); 를 호출 하도록 하였습니다.

Swift에서는 아래와 같이 각 메세지를 받아서 분기 처리를 할 수 있습니다.

func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {
    if message == "ajax" {
        // Ajax 통신 시 웹뷰의 URL을 변경 시키는 경우가 있으므로 이를 확인.
        // Ajax 통신이 이루어지는 URL과는 다름.
        print(webView.URL?.absoluteString)
        completionHandler()

        return
    }
    else if message == "ajaxDone" {
        // Ajax 통신으로 달라 진 HTML (DOM) 을 확인.
        self.webKit.evaluateJavaScript("document.documentElement.outerHTML", completionHandler: { (object, error) in
            print(object)
        })
        completionHandler()

        return
    }

    // UIAlertController 사용 message 내용 띄우기.

    completionHandler()
}

이제 WKWebView는 Ajax를 포함한 모든 통신을 감지할 수 있게 되었습니다.

단순히 웹뷰에 웹을 보여 주는 한계에서 벗어 나 앱의 제어권을 더 넒힌 하이브리드앱의 제작이 가능해졌습니다.