Help us understand the problem. What is going on with this article?

iOS9, 10 WKWebView - Cookie操作

More than 3 years have 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の実装はまた追記します😓

ysakui
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away