15
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

iOS9, 10 WKWebView - Cookie操作

Last updated at Posted at 2017-09-06

ブラウザ機能を持つアプリの開発で、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の実装はまた追記します😓

15
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?