ブラウザ機能を持つアプリの開発で、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 は通知されない
自前でタイマー使うか適切なタイミングで参照して変更を検知する
サンプルコード
まとめ
- 共有のWKProcessPoolを使う
- ダミーURLを使い、ローカルHTMLを読み込む
- 通信は発生しないため、オフラインでの参照・更新が可能
- addSubView不要
- 参照時はJavaScriptでnameやexpireを指定か、document.cookieで取得した結果をパースして使う
他にも良い方法あればぜひ教えてください。
iOS11の実装はまた追記します😓