Edited at

iOS9, 10 WKWebView - Cookie操作

More than 1 year has passed since last update.

ブラウザ機能を持つアプリの開発で、UIWebViewからWKWebViewへ移行した際にCookie操作で詰まったので手順をまとめておきます。

様々な方法を試した結果、最終的に下記方法に落ち着きました。


WKWebView間のCookie同期

複数のWKWebViewを使う場合は、WKProcessPoolを共有することでリアルタイムでCookieの同期が可能。


ViewController.swift


import WebKit

final class ViewController: UIViewController {

private lazy var sharedProcessPool: WKProcessPool = WKProcessPool()
private var webView: WKWebView?

func setUpWKWebView() {
let configuration = WKWebViewConfiguration()

// 共有しているprocessPoolを使う
configuration.processPool = sharedProcessPool

let webView = WKWebView(frame: .zero, configuration: configuration)

webView.translatesAutoresizingMaskIntoConstraints = false

view.addSubview(webView)

NSLayoutConstraint.activate(
[
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
]
)

self.webView = webView
}
}



[iOS9, 10] Cookieの参照

iOS9, 10では直接参照する方法がないので、WKUserScriptを使いJavaScript経由で取得する

let getCookiesStringHandler = "getCookiesStringHandler"

fileprivate var cookiesStringCompletion: ((_ cookiesString: String?) -> Void)?

// JavaScriptで document.cookie 結果を取得する
func getCookiesString(completion: @escaping (_ cookiesString: String?) -> Void) {
self.cookiesStringCompletion = completion

let htmlTemplate = "<DOCTYPE html><html><body></body></html>"
let dummyBaseURL = URL(string: "https://dummy.hoge.fuga")
let javaScriptString = "webkit.messageHandlers.\(self.getCookiesStringHandler).postMessage(document.cookie)"

let userScript = WKUserScript(
source: javaScriptString,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true
)

let controller = WKUserContentController()
controller.addUserScript(userScript)
controller.add(self, name: self.getCookiesStringHandler)

let configuration = WKWebViewConfiguration()
configuration.userContentController = controller
configuration.processPool = self.sharedProcessPool

let webView = WKWebView(frame: .zero, configuration: configuration)
webView.loadHTMLString(htmlTemplate, baseURL: dummyBaseURL)

self.webView = webView
}

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
self.webView?.stopLoading()
self.webView = nil

if message.name == self.getCookiesStringHandler {
if let cookiesString = message.body as? String {
self.cookiesStringCompletion?(cookiesString)
} else {
self.cookiesStringCompletion?(nil)
}
}
}


[iOS9, 10] Cookieの更新

基本cookieを更新したい場合は、サーバーサイドで行えば良いので発行・更新するエントリポイントにWKWebViewでloadRequestすれば良い。

ローカルで更新が必要な場合は、参照時と同様にWKUserScriptを使ってJavaScript経由で更新する

let setCookieHandler = "setCookieHandler"

fileprivate var setCookieCompletion: (() -> Void)?

// JavaScriptで document.cookie を使って更新する
func setCookie(name: String, value: String, completion: @escaping () -> Void) {
self.setCookieCompletion = completion

let htmlTemplate = "<DOCTYPE html><html><body></body></html>"
let dummyBaseURL = URL(string: "https://dummy.hoge.fuga")
let javaScriptString = "webkit.messageHandlers.\(self.setCookieHandler).postMessage(document.cookie='\(name)=\(value)')"

let userScript = WKUserScript(
source: javaScriptString,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true
)

let controller = WKUserContentController()
controller.addUserScript(userScript)
controller.add(self, name: self.setCookieHandler)

let configuration = WKWebViewConfiguration()
configuration.userContentController = controller
configuration.processPool = self.sharedProcessPool

let webView = WKWebView(frame: .zero, configuration: configuration)

webView.loadHTMLString(htmlTemplate, baseURL: dummyBaseURL)

self.webView = webView
}

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
self.webView?.stopLoading()
self.webView = nil

if message.name == self.setCookieHandler {
self.setCookieCompletion?()
}
}


[iOS9, 10] Cookieの監視

WKWebViewのcookieはWKWebsiteDataStoreで管理されており、iOS9, 10では更新があってもNSHTTPCookieManagerCookiesChanged は通知されない

自前でタイマー使うか適切なタイミングで参照して変更を検知する


サンプルコード

https://github.com/ysakui/WKWebViewCookieSync


まとめ


  • 共有のWKProcessPoolを使う

  • ダミーURLを使い、ローカルHTMLを読み込む


    • 通信は発生しないため、オフラインでの参照・更新が可能



  • addSubView不要

  • 参照時はJavaScriptでnameやexpireを指定か、document.cookieで取得した結果をパースして使う

他にも良い方法あればぜひ教えてください。

iOS11の実装はまた追記します😓