WKWebView Persistent Storage of Cookies

后端 未结 8 1193
时光说笑
时光说笑 2020-11-29 22:22

I am using a WKWebView in my native iPhone application, on a website that allows login/registration, and stores the session information in cookies. I am tryin

8条回答
  •  心在旅途
    2020-11-29 23:05

    I'm a little late in answering this but I would like to add some insights to the existing answers. The answer already mentioned here already provides valuable information into Cookie Persistence on WKWebView. There are a few caveats though.

    1. WKWebView doesn't work well with NSHTTPCookieStorage, so for iOS 8, 9, 10 you will have to use UIWebView.
    2. You don't need to necessarily hold on to the WKWebView as a singleton but you do need to use the same instance of WKProcessPool every time to get the desired cookies again.
    3. It is preferable to set the cookies first using the setCookie method and then instantiate the WKWebView.

    I would also like to highlight the iOS 11+ Solution in Swift.

    let urlString = "http://127.0.0.1:8080"
    var webView: WKWebView!
    let group = DispatchGroup()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setupWebView { [weak self] in
            self?.loadURL()
        }
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        if #available(iOS 11.0, *) {
            self.webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
                self.setData(cookies, key: "cookies")
            }
        } else {
            // Fallback on earlier versions
        }
    }
    
    private func loadURL() {
        let urlRequest = URLRequest(url: URL(string: urlString)!)
        self.webView.load(urlRequest)
    }
    
    private func setupWebView(_ completion: @escaping () -> Void) {
    
        func setup(config: WKWebViewConfiguration) {
            self.webView = WKWebView(frame: CGRect.zero, configuration: config)
            self.webView.navigationDelegate = self
            self.webView.uiDelegate = self
            self.webView.translatesAutoresizingMaskIntoConstraints = false
            self.view.addSubview(self.webView)
    
            NSLayoutConstraint.activate([
                self.webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
                self.webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
                self.webView.topAnchor.constraint(equalTo: self.view.topAnchor),
                self.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)])
        }
    
        self.configurationForWebView { config in
            setup(config: config)
            completion()
        }
    
    }
    
    private func configurationForWebView(_ completion: @escaping (WKWebViewConfiguration) -> Void) {
    
        let configuration = WKWebViewConfiguration()
    
        //Need to reuse the same process pool to achieve cookie persistence
        let processPool: WKProcessPool
    
        if let pool: WKProcessPool = self.getData(key: "pool")  {
            processPool = pool
        }
        else {
            processPool = WKProcessPool()
            self.setData(processPool, key: "pool")
        }
    
        configuration.processPool = processPool
    
        if let cookies: [HTTPCookie] = self.getData(key: "cookies") {
    
            for cookie in cookies {
    
                if #available(iOS 11.0, *) {
                    group.enter()
                    configuration.websiteDataStore.httpCookieStore.setCookie(cookie) {
                        print("Set cookie = \(cookie) with name = \(cookie.name)")
                        self.group.leave()
                    }
                } else {
                    // Fallback on earlier versions
                }
            }
    
        }
    
        group.notify(queue: DispatchQueue.main) {
            completion(configuration)
        }
    }
    

    Helper methods:

    func setData(_ value: Any, key: String) {
        let ud = UserDefaults.standard
        let archivedPool = NSKeyedArchiver.archivedData(withRootObject: value)
        ud.set(archivedPool, forKey: key)
    }
    
    func getData(key: String) -> T? {
        let ud = UserDefaults.standard
        if let val = ud.value(forKey: key) as? Data,
            let obj = NSKeyedUnarchiver.unarchiveObject(with: val) as? T {
            return obj
        }
    
        return nil
    }
    

    Edit: I had mentioned that it's preferable to instantiate WKWebView post setCookie calls. I ran into some issues wherein the setCookie completion handlers were not getting called the second time I tried to open the WKWebView. This seems to a be a bug in the WebKit. Therefore, I had to instantiate WKWebView first and then call setCookie on the configuration. Make sure to load the URL only after all the setCookie calls have returned.

提交回复
热议问题