【问题标题】:Can I set the cookies to be used by a WKWebView?我可以设置 WKWebView 使用的 cookie 吗?
【发布时间】:2014-12-21 18:43:27
【问题描述】:

我正在尝试将现有应用从 UIWebView 切换到 WKWebView。当前应用程序在webview 之外管理用户登录/会话,并将身份验证所需的cookies 设置为NSHTTPCookieStore。不幸的是,新的WKWebView 不使用NSHTTPCookieStorage 中的cookies。还有其他方法可以实现吗?

【问题讨论】:

    标签: ios cookies uiwebview ios8 wkwebview


    【解决方案1】:

    仅适用于 iOS 11+ 的编辑

    使用WKHTTPCookieStore:

    let cookie = HTTPCookie(properties: [
        .domain: "example.com",
        .path: "/",
        .name: "MyCookieName",
        .value: "MyCookieValue",
        .secure: "TRUE",
        .expires: NSDate(timeIntervalSinceNow: 31556926)
    ])! 
    
    webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
    

    由于您是从 HTTPCookeStorage 中提取它们,因此您可以这样做:

    let cookies = HTTPCookieStorage.shared.cookies ?? []
    for cookie in cookies {
        webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
    }
    

    iOS 10 及更低版本的旧答案

    如果您需要在初始加载请求上设置 cookie,您可以在 NSMutableURLRequest 上设置它们。因为 cookie 只是一个特殊格式的请求标头,所以可以这样实现:

    WKWebView * webView = /*set up your webView*/
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/index.html"]];
    [request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"];
    // use stringWithFormat: in the above line to inject your values programmatically
    [webView loadRequest:request];
    

    如果您需要页面上的后续 AJAX 请求来设置其 cookie,这可以通过简单地使用 WKUserScript 在文档开始时通过 javascript 以编程方式设置值来实现,如下所示:

    WKUserContentController* userContentController = WKUserContentController.new;
    WKUserScript * cookieScript = [[WKUserScript alloc] 
        initWithSource: @"document.cookie = 'TeskCookieKey1=TeskCookieValue1';document.cookie = 'TeskCookieKey2=TeskCookieValue2';"
        injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    // again, use stringWithFormat: in the above line to inject your values programmatically
    [userContentController addUserScript:cookieScript];
    WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
    webViewConfig.userContentController = userContentController;
    WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake(/*set your values*/) configuration:webViewConfig];
    

    结合这两种技术应该可以为您提供足够的工具来将 cookie 值从 Native App Land 传输到 Web View Land。如果您需要一些更高级的 cookie,您可以在 cookie javascript API on Mozilla's page 上找到更多信息。

    是的,Apple 不支持the niceties of UIWebView 的许多内容,这很糟糕。不确定他们是否会支持他们,但希望他们能尽快开始。希望这会有所帮助!

    【讨论】:

    • 为后续请求注入 cookie 的最佳位置在哪里?例如。初始页面加载包含在上面的答案中,但是如果页面上的链接也指向相同的域,并且还需要将相同的 cookie 注入到请求中怎么办? didStartProvisionalNavigation?
    • 抱歉,它不适合您。在我看来,只要域相同,就不应该有任何问题。您能否再次检查链接指向与您加载请求的域相同的代码?此外,cookie 也可以被限制到特定的“路径”。也许这会导致一些问题?
    • 请注意,设置 cookie 的 javascript 技术不适用于“仅限 HTTP”cookie。
    • 上述方法效果很好......但我可以看到在随后的 AJAX 调用中重复的 cookie(仅重复一次)。
    • webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie) 这不适用于 ios13,还有其他人面临这个问题吗?
    【解决方案2】:

    为我工作

    func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
        let headerFields = navigationAction.request.allHTTPHeaderFields
        var headerIsPresent = contains(headerFields?.keys.array as! [String], "Cookie")
    
        if headerIsPresent {
            decisionHandler(WKNavigationActionPolicy.Allow)
        } else {
            let req = NSMutableURLRequest(URL: navigationAction.request.URL!)
            let cookies = yourCookieData
            let values = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies)
            req.allHTTPHeaderFields = values
            webView.loadRequest(req)
    
            decisionHandler(WKNavigationActionPolicy.Cancel)
        }
    }
    

    【讨论】:

    • 很棒的 hack,尤其是 iOS 是多么顽固地要覆盖现有 WKWebView 中的 cookie。唯一的问题是以前的 WKNavigationKey 已经过时了。其他代码可能会在旧代码上徒劳地等待。
    • 这是正确的吗?欣赏它在某些情况下可能会起作用。然而,这个委托方法的职责——decisionPolicyForNavigationAction——是决定策略;不要实际加载请求。这是以前发起的。在什么情况下,这不会导致请求被加载两次吗?
    • @MaxMacLeod 在else 条件下,他使用.cancel 调用decisionHandler 闭包,因此webview 实际上并没有加载初始请求。在loadRequestelse 条件中调用后,此委托方法将再次为该请求调用,它将进入if 条件,因为Cookie 标头将存在。
    • 虽然这在初始请求已经设置了一些 cookie 时不起作用,因为它永远不会进入 else 条件。
    • 请注意,这是 1) 不适用于所有情况 - 例如,当 Web 视图加载帧时 2) 不安全 - 它可能会将带有敏感信息的 cookie 发送到第 3 方 URL
    【解决方案3】:

    在使用this answer(这非常有帮助:)之后,我们不得不做出一些改变:

    • 我们需要 Web 视图来处理多个域,而不会在这些域之间泄露私有 cookie 信息
    • 我们需要它来支持安全 cookie
    • 如果服务器更改了 cookie 值,我们希望我们的应用程序在 NSHTTPCookieStorage 中知道它
    • 如果服务器更改了 cookie 值,我们不希望我们的脚本在您点击链接/AJAX 等时将其重置为原始值。

    所以我们修改我们的代码是这样的;

    创建请求

    NSMutableURLRequest *request = [originalRequest mutableCopy];
    NSString *validDomain = request.URL.host;
    const BOOL requestIsSecure = [request.URL.scheme isEqualToString:@"https"];
    
    NSMutableArray *array = [NSMutableArray array];
    for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
        // Don't even bother with values containing a `'`
        if ([cookie.name rangeOfString:@"'"].location != NSNotFound) {
            NSLog(@"Skipping %@ because it contains a '", cookie.properties);
            continue;
        }
    
        // Is the cookie for current domain?
        if (![cookie.domain hasSuffix:validDomain]) {
            NSLog(@"Skipping %@ (because not %@)", cookie.properties, validDomain);
            continue;
        }
    
        // Are we secure only?
        if (cookie.secure && !requestIsSecure) {
            NSLog(@"Skipping %@ (because %@ not secure)", cookie.properties, request.URL.absoluteString);
            continue;
        }
    
        NSString *value = [NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value];
        [array addObject:value];
    }
    
    NSString *header = [array componentsJoinedByString:@";"];
    [request setValue:header forHTTPHeaderField:@"Cookie"];
    
    // Now perform the request...
    

    这可确保第一个请求设置了正确的 cookie,不会从共享存储中发送任何用于其他域的 cookie,也不会将任何安全 cookie 发送到不安全的请求中。

    处理进一步的请求

    我们还需要确保其他请求设置了 cookie。这是使用在文档加载时运行的脚本完成的,该脚本检查是否有 cookie 集,如果没有,请将其设置为 NSHTTPCookieStorage 中的值。

    // Get the currently set cookie names in javascriptland
    [script appendString:@"var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );\n"];
    
    for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
        // Skip cookies that will break our script
        if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
            continue;
        }
    
        // Create a line that appends this cookie to the web view's document's cookies
        [script appendFormat:@"if (cookieNames.indexOf('%@') == -1) { document.cookie='%@'; };\n", cookie.name, cookie.wn_javascriptString];
    }
    
    WKUserContentController *userContentController = [[WKUserContentController alloc] init];
    WKUserScript *cookieInScript = [[WKUserScript alloc] initWithSource:script
                                                          injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                       forMainFrameOnly:NO];
    [userContentController addUserScript:cookieInScript];
    

    ...

    // Create a config out of that userContentController and specify it when we create our web view.
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    config.userContentController = userContentController;
    
    self.webView = [[WKWebView alloc] initWithFrame:webView.bounds configuration:config];
    

    处理 cookie 更改

    我们还需要处理服务器更改 cookie 的值。这意味着添加另一个脚本来回调我们正在创建的网络视图以更新我们的NSHTTPCookieStorage

    WKUserScript *cookieOutScript = [[WKUserScript alloc] initWithSource:@"window.webkit.messageHandlers.updateCookies.postMessage(document.cookie);"
                                                           injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                        forMainFrameOnly:NO];
    [userContentController addUserScript:cookieOutScript];
    
    [userContentController addScriptMessageHandler:webView
                                              name:@"updateCookies"];
    

    并实现委托方法来更新任何已更改的 cookie,确保我们只更新来自当前域的 cookie!

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
        NSArray<NSString *> *cookies = [message.body componentsSeparatedByString:@"; "];
        for (NSString *cookie in cookies) {
            // Get this cookie's name and value
            NSArray<NSString *> *comps = [cookie componentsSeparatedByString:@"="];
            if (comps.count < 2) {
                continue;
            }
    
            // Get the cookie in shared storage with that name
            NSHTTPCookie *localCookie = nil;
            for (NSHTTPCookie *c in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:self.wk_webView.URL]) {
                if ([c.name isEqualToString:comps[0]]) {
                    localCookie = c;
                    break;
                }
            }
    
            // If there is a cookie with a stale value, update it now.
            if (localCookie) {
                NSMutableDictionary *props = [localCookie.properties mutableCopy];
                props[NSHTTPCookieValue] = comps[1];
                NSHTTPCookie *updatedCookie = [NSHTTPCookie cookieWithProperties:props];
                [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:updatedCookie];
            }
        }
    }
    

    这似乎解决了我们的 cookie 问题,而我们不必处理我们使用 WKWebView 不同的每个地方。我们现在可以只使用此代码作为帮助器来创建我们的 Web 视图,它会透明地为我们更新 NSHTTPCookieStorage


    编辑:原来我在 NSHTTPCookie 上使用了一个私有类别 - 这是代码:

    - (NSString *)wn_javascriptString {
        NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",
                            self.name,
                            self.value,
                            self.domain,
                            self.path ?: @"/"];
    
        if (self.secure) {
            string = [string stringByAppendingString:@";secure=true"];
        }
    
        return string;
    }
    

    【讨论】:

    • 我已将您的代码包装到 WKWebView 的子类中。欢迎随时查看github.com/haifengkao/YWebView
    • 如果您的 cookie 在值中包含 = 符号怎么办?这行得通吗?
    • @iOSAddicted 我认为是这样。如果您的值是a=b,那么您最终会得到 cookie 字符串 name=a=b;domain=.example.com;path=/ - 我相信标准会在 ; 上拆分,然后在 key=value 中的 first = 上拆分一对。不过我会对此进行测试:)
    • 您的回复对我帮助很大,不过我想在您的帖子中添加一些内容,使用您的更新方法存在一些风险,一些 JS 框架可能会创建具有相同名称但不同域的 cookie ,并且如果您尝试使用 js 方法更新它,则更新 cookie 值错误的风险很高。对于我们来说,js cookie 字符串也必须去掉它的安全标志,因为我们的服务器在 http 和 https 之间进行令人讨厌的重定向,导致在某些讨厌的边缘情况下某些页面中不存在安全 cookie。
    • 我实际上认为我写这篇文章时所在的公司确实必须在它上线后为其添加一些域保护。我们从来没有(afaik)遇到过安全/不安全的问题——听起来就像一场噩梦!
    【解决方案4】:

    这是我在 Swift 中的 Mattrs 解决方案版本,用于从 HTTPCookieStorage 注入所有 cookie。这样做主要是为了注入身份验证 cookie 来创建用户会话。

    public func setupWebView() {
        let userContentController = WKUserContentController()
        if let cookies = HTTPCookieStorage.shared.cookies {
            let script = getJSCookiesString(for: cookies)
            let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
            userContentController.addUserScript(cookieScript)
        }
        let webViewConfig = WKWebViewConfiguration()
        webViewConfig.userContentController = userContentController
    
        self.webView = WKWebView(frame: self.webViewContainer.bounds, configuration: webViewConfig)
    }
    
    ///Generates script to create given cookies
    public func getJSCookiesString(for cookies: [HTTPCookie]) -> String {
        var result = ""
        let dateFormatter = DateFormatter()
        dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
        dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"
    
        for cookie in cookies {
            result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
            if let date = cookie.expiresDate {
                result += "expires=\(dateFormatter.stringFromDate(date)); "
            }
            if (cookie.secure) {
                result += "secure; "
            }
            result += "'; "
        }
        return result
    }
    

    【讨论】:

    • 最好添加此行以确保区域设置格式正确:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
    • 在哪里调用这个?
    • 这在 Swift 4 中对我来说效果很好(稍作调整)
    • 这对我很有用,但仅在我第二次访问该网站时(第一次未设置 cookie)- 有人遇到过这个吗?
    • 第一次加载给出错误第二次加载工作:(可能是什么问题?
    【解决方案5】:

    设置 cookie

    self.webView.evaluateJavaScript("document.cookie='access_token=your token';domain='your domain';") { (data, error) -> Void in
            self.webView.reload()
    }
    

    删除 cookie

    self.webView.evaluateJavaScript("document.cookie='access_token=';domain='your domain';") { (data, error) -> Void in
            self.webView.reload()
    }
    

    【讨论】:

    • reload 会在什么时候调用?加载 webview 后,您不认为这对用户不友好吗?您正在重新加载 webview。
    【解决方案6】:

    Swift 3 更新:

    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        if let urlResponse = navigationResponse.response as? HTTPURLResponse,
           let url = urlResponse.url,
           let allHeaderFields = urlResponse.allHeaderFields as? [String : String] {
           let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHeaderFields, for: url)
           HTTPCookieStorage.shared.setCookies(cookies , for: urlResponse.url!, mainDocumentURL: nil)
           decisionHandler(.allow)
        }
    }
    

    【讨论】:

    • 您好,您可以添加代码以从 HTTPCookieStorage.shared 获取 cookie 吗?
    • 这是我让 WKWebView 向 webview 发出的每个请求添加 cookie 的唯一方法
    • 如果响应中包含httponly cookie,则无法通过这种方式获取cookie值。
    • 这只是将cookie设置回httpcookies存储在哪里为wkwebview设置cookie的代码?
    【解决方案7】:

    在 iOS 11 中,您现在可以管理 cookie :),请参阅此会话:https://developer.apple.com/videos/play/wwdc2017/220/

    【讨论】:

    • @ShobhakarTiwari 为什么? iOS11正式发布有变化吗?
    • 如果您只支持 iOS 11 及更高版本,最好的方式,如果您需要支持以前的版本,请在页面加载之前使用 JavaScript。
    • 这对我有用,除了有时 setcookie 方法不运行它的完成处理程序,这意味着有时我的网页不加载 - 只发生在设备上,发生在第 3 次/第 4 次/第 5 次关闭并重新打开 web 视图,并且在它发生一次之后,它一直在发生,直到我重置应用程序 - 有人也遇到过这种情况吗?
    【解决方案8】:

    请找到最有可能开箱即用的解决方案。基本上它是为 Swift 4 @user3589213answer 修改和更新的。

    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        let headerKeys = navigationAction.request.allHTTPHeaderFields?.keys
        let hasCookies = headerKeys?.contains("Cookie") ?? false
    
        if hasCookies {
            decisionHandler(.allow)
        } else {
            let cookies = HTTPCookie.requestHeaderFields(with: HTTPCookieStorage.shared.cookies ?? [])
    
            var headers = navigationAction.request.allHTTPHeaderFields ?? [:]
            headers += cookies
    
            var req = navigationAction.request
            req.allHTTPHeaderFields = headers
    
            webView.load(req)
    
            decisionHandler(.cancel)
        }
    }
    

    【讨论】:

      【解决方案9】:

      必须在创建 WKWebView 之前在配置中设置 cookie。否则,即使使用WKHTTPCookieStoresetCookie 完成处理程序,cookie 也不会可靠地同步到 Web 视图。这从WKWebViewConfiguration上的docs回到这一行

      @NSCopying var configuration: WKWebViewConfiguration { get }
      

      @NSCopying 有点深拷贝。该实现超出了我的范围,但最终结果是,除非您在初始化 web 视图之前设置 cookie,否则您不能指望那里的 cookie。这会使应用程序架构复杂化,因为初始化视图变成了一个异步过程。你最终会得到这样的东西

      extension WKWebViewConfiguration {
          /// Async Factory method to acquire WKWebViewConfigurations packaged with system cookies
          static func cookiesIncluded(completion: @escaping (WKWebViewConfiguration?) -> Void) {
              let config = WKWebViewConfiguration()
              guard let cookies = HTTPCookieStorage.shared.cookies else {
                  completion(config)
                  return
              }
              // Use nonPersistent() or default() depending on if you want cookies persisted to disk
              // and shared between WKWebViews of the same app (default), or not persisted and not shared
              // across WKWebViews in the same app.
              let dataStore = WKWebsiteDataStore.nonPersistent()
              let waitGroup = DispatchGroup()
              for cookie in cookies {
                  waitGroup.enter()
                  dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
              }
              waitGroup.notify(queue: DispatchQueue.main) {
                  config.websiteDataStore = dataStore
                  completion(config)
              }
          }
      }
      

      然后使用它像

      override func loadView() {
          view = UIView()
          WKWebViewConfiguration.cookiesIncluded { [weak self] config in
              let webView = WKWebView(frame: .zero, configuration: webConfiguration)
              webView.load(request)
              self.view = webView
          }
      }
      

      上述示例将视图创建推迟到最后一刻,另一种解决方案是提前创建配置或 webview,并在创建视图控制器之前处理异步性质。

      最后一点:一旦你创建了这个 webview,你就已经把它放开了,如果不使用this answer 中描述的方法,你就不能添加更多的 cookie。但是,您可以使用WKHTTPCookieStoreObserver api 至少观察 cookie 发生的变化。因此,如果在 web 视图中更新了会话 cookie,如果需要,您可以使用此新 cookie 手动更新系统的 HTTPCookieStorage

      如需了解更多信息,请通过2017 WWDC Session Custom Web Content Loading 跳至 18:00。在本次会议开始时,有一个欺骗性的代码示例,它忽略了应该在完成处理程序中创建 webview 的事实。

      cookieStore.setCookie(cookie!) {
          webView.load(loggedInURLRequest)
      }
      

      18:00 的现场演示澄清了这一点。

      编辑至少从 Mojave Beta 7 和 iOS 12 Beta 7 开始,我看到 cookie 的行为更加一致。 setCookie(_:) 方法甚至似乎允许在创建 WKWebView 之后设置 cookie。我确实发现它很重要,不要触摸 processPool 变量。 cookie 设置功能在没有创建其他池并且该属性被单独保留时效果最佳。我认为可以肯定地说,由于 WebKit 中的一些错误,我们遇到了问题。

      【讨论】:

      • 看起来 cookie 处理/设置在 Mojave 10.14 beta 3 和 iOS 12 beta 3 中更可靠
      • 非常深入且被低估的答案
      • 我在 iOS 12 中仍然遇到这个问题,并且已经加载了 WKWebView。有时 setCookie() 实际上会立即与 WKWebView 同步,有时它不会使处理有些零星
      • 自从雷达声称已修复以来,我仍然看到了一些问题,但频率要低得多。您多久看到一次 cookie 失败?如果你有一个可重复的、足够小的项目,我真的建议在这里提交一个 webkit 错误:webkit.org/reporting-bugs 你也可以发推文Brady Eidson(很好)一位苹果的 webkit 架构师,他对这些类型的报告和错误非常敏感.
      • 这是正确答案 - 无需手动将 cookie 转换为每个 URLRequest 中的标头字段,只是需要按照此处所述使用 setCookie()。
      【解决方案10】:

      XHR 请求的更好修复显示在here

      Swift 4 版本:

      func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Swift.Void) {
          guard
              let response = navigationResponse.response as? HTTPURLResponse,
              let url = navigationResponse.response.url
          else {
              decisionHandler(.cancel)
              return
          }
      
          if let headerFields = response.allHeaderFields as? [String: String] {
              let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url)
              cookies.forEach { (cookie) in
                  HTTPCookieStorage.shared.setCookie(cookie)
              }
          }
      
          decisionHandler(.allow)
      }
      

      【讨论】:

        【解决方案11】:

        在这里查看各种答案但没有任何成功后,我梳理了 WebKit 文档并偶然发现了 HTTPCookie 上的 requestHeaderFields 静态方法,该方法将 cookie 数组转换为适合标头字段的格式。将此与 mattr's insight 结合使用更新 URLRequest,然后再将其与 cookie 标头一起加载,让我通过了终点线。

        斯威夫特 4.1、4.2、5.0:

        var request = URLRequest(url: URL(string: "https://example.com/")!)
        let headers = HTTPCookie.requestHeaderFields(with: cookies)
        for (name, value) in headers {
            request.addValue(value, forHTTPHeaderField: name)
        }
        
        let webView = WKWebView(frame: self.view.frame)
        webView.load(request)
        

        为了更简单,请使用扩展:

        extension WKWebView {
            func load(_ request: URLRequest, with cookies: [HTTPCookie]) {
                var request = request
                let headers = HTTPCookie.requestHeaderFields(with: cookies)
                for (name, value) in headers {
                    request.addValue(value, forHTTPHeaderField: name)
                }
        
                load(request)
            }
        }
        

        现在变成了:

        let request = URLRequest(url: URL(string: "https://example.com/")!)
        let webView = WKWebView(frame: self.view.frame)
        webView.load(request, with: cookies)
        

        如果您只想要一个插入式解决方案,此扩展程序也可以在 LionheartExtensions 中使用。干杯!

        【讨论】:

        • @ShauketSheikh 嗯,这在什么情况下不起作用?
        • 我使用模拟器 ios 8 进行了测试,它似乎没有发送 cookie。我已经仔细检查了。
        • 我发布了我的答案,你可以试试@Dan
        【解决方案12】:

        发布这个答案背后的原因是我尝试了很多解决方案,但没有一个能正常工作,如果必须第一次设置 cookie,大多数答案都不起作用,并且第一次得到结果 cookie 不同步,请使用这个解决方案适用于 iOS >= 11.0

        对于 iOS >= 11.0 -- 斯威夫特 4.2

        像这样获取http cookie并设置在wkwebview cookie存储中,在wkwebview中加载您的请求非常棘手,必须发送完全设置 cookie 时的加载请求,这是我编写的函数。

        调用带有 closure 的函数,完成后调用加载 webview。仅供参考,此功能仅处理 iOS >= 11.0

        self.WwebView.syncCookies {
            if let request = self.request {
               self.WwebView.load(request)
            }
        }
        

        这里是 syncCookies 函数的实现。

        func syncCookies(completion:@escaping ()->Void) {
        
        if #available(iOS 11.0, *) {
        
              if let yourCookie = "HERE_YOUR_HTTP_COOKIE_OBJECT" {
                self.configuration.websiteDataStore.httpCookieStore.setCookie(yourCookie, completionHandler: {
                      completion()
                })
             }
          } else {
          //Falback just sent 
          completion()
        }
        }
        

        适用于 iOS 8 至 iOS 11

        你需要设置一些额外的东西,你需要通过使用 WKUserScript 设置两次 cookie,并且不要忘记在请求中添加 cookie,否则你的 cookie 不会第一次同步,你会看到您的页面第一次未正确加载。这就是我发现支持 iOS 8.0 的 cookie

        在你创建 Wkwebview 对象之前。

        func setUpWebView() {
        
            let userController: WKUserContentController = WKUserContentController.init()
        
            if IOSVersion.SYSTEM_VERSION_LESS_THAN(version: "11.0") {
                if let cookies = HTTPCookieStorage.shared.cookies {
                    if let script = getJSCookiesString(for: cookies) {
                        cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
                        userController.addUserScript(cookieScript!)
                    }
                }
            }
        
            let webConfiguration = WKWebViewConfiguration()
            webConfiguration.processPool = BaseWebViewController.processPool
        
        
            webConfiguration.userContentController = userController
        
        
            let customFrame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: 0.0, height: self.webContainerView.frame.size.height))
            self.WwebView = WKWebView (frame: customFrame, configuration: webConfiguration)
            self.WwebView.translatesAutoresizingMaskIntoConstraints = false
            self.webContainerView.addSubview(self.WwebView)
            self.WwebView.uiDelegate = self
            self.WwebView.navigationDelegate = self
            self.WwebView.allowsBackForwardNavigationGestures = true // A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations
            self.WwebView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
        
        
         self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .trailing, relatedBy: .equal, toItem: self.webContainerView, attribute: .trailing, multiplier: 1, constant: 0))
            self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .leading, relatedBy: .equal, toItem: self.webContainerView, attribute: .leading, multiplier: 1, constant: 0))
            self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .top, relatedBy: .equal, toItem: self.webContainerView, attribute: .top, multiplier: 1, constant: 0))
            self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .bottom, relatedBy: .equal, toItem: self.webContainerView, attribute: .bottom, multiplier: 1, constant: 0))
        
        
        }
        

        关注这个函数getJSCookiesString

         public func getJSCookiesString(for cookies: [HTTPCookie]) -> String? {
        
            var result = ""
            let dateFormatter = DateFormatter()
            dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
            dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"
        
            for cookie in cookies {
                if cookie.name == "yout_cookie_name_want_to_sync" {
                    result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
                    if let date = cookie.expiresDate {
                        result += "expires=\(dateFormatter.string(from: date)); "
                    }
                    if (cookie.isSecure) {
                        result += "secure; "
                    }
                    result += "'; "
                }
        
            }
        
            return result
        }
        

        这是另一个步骤 wkuserscript 不会立即同步 cookie,第一次加载带有 cookie 的页面有很多麻烦,如果它终止进程,则重新加载 webview 但我不建议使用它,这对用户不利观点,见鬼的是,每当你准备好在请求头中加载请求集cookie时,就像这样,不要忘记添加iOS版本检查。在加载请求之前调用这个函数。

        request?.addCookies()
        

        我为 URLRequest 写了扩展

        extension URLRequest {
        
        internal mutating func addCookies() {
            //"appCode=anAuY28ucmFrdXRlbi5yZXdhcmQuaW9zLXpOQlRTRmNiejNHSzR0S0xuMGFRb0NjbUg4Ql9JVWJH;rpga=kW69IPVSYZTo0JkZBicUnFxC1g5FtoHwdln59Z5RNXgJoMToSBW4xAMqtf0YDfto;rewardadid=D9F8CE68-CF18-4EE6-A076-CC951A4301F6;rewardheader=true"
            var cookiesStr: String = ""
        
            if IOSVersion.SYSTEM_VERSION_LESS_THAN(version: "11.0") {
                let mutableRequest = ((self as NSURLRequest).mutableCopy() as? NSMutableURLRequest)!
                if let yourCookie = "YOUR_HTTP_COOKIE_OBJECT" {
                    // if have more than one cookies dont forget to add ";" at end
                    cookiesStr += yourCookie.name + "=" + yourCookie.value + ";"
        
                    mutableRequest.setValue(cookiesStr, forHTTPHeaderField: "Cookie")
                    self = mutableRequest as URLRequest
        
                }
            }
        
          }
        }
        

        现在您可以开始测试 iOS > 8

        【讨论】:

          【解决方案13】:

          如果有人在使用 Alamofire,那么这是更好的解决方案。

            let cookies = Alamofire.SessionManager.default.session.configuration.httpCookieStorage?.cookies(for: URL(string: BASE_URL)!)
            for (cookie) in cookies ?? [] {
                webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
            }
          

          【讨论】:

            【解决方案14】:

            这对我有用: 在 setcookies 之后,添加 fetchdatarecords

               let cookiesSet = NetworkProvider.getCookies(forKey : 
                PaywallProvider.COOKIES_KEY, completionHandler: nil)
                            let dispatchGroup = DispatchGroup()
                            for (cookie) in cookiesSet {
                                if #available(iOS 11.0, *) {
                                    dispatchGroup.enter()
                                    self.webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie){
                                        dispatchGroup.leave()
                                        print ("cookie added: \(cookie.description)")
                                        }
                                    } else {
                                                        // TODO Handle ios 10 Fallback on earlier versions
                                    }
                                }
                                dispatchGroup.notify(queue: .main, execute: {
            
            
                self.webView.configuration.websiteDataStore.fetchDataRecords(ofTypes: 
                WKWebsiteDataStore.allWebsiteDataTypes()) { records in
                                        records.forEach { record in
            
                                            print("[WebCacheCleaner] Record \(record)")
                                        }
                                        self.webView.load(URLRequest(url: 
                self.dataController.premiumArticleURL , 
                cachePolicy:NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData,
                                                                     timeoutInterval: 10.0))
                                    }
            
                                })
                            }
            

            【讨论】:

              【解决方案15】:

              我已经尝试了上述所有答案,但没有一个有效。经过这么多尝试,我终于找到了一种设置 WKWebview cookie 的可靠方法。

              首先,您必须创建一个 WKProcessPool 实例并将其设置为用于初始化 WkWebview 本身的 WKWebViewConfiguration:

                  private lazy var mainWebView: WKWebView = {
                      let webConfiguration = WKWebViewConfiguration()
                      webConfiguration.processPool = WKProcessPool()
                      let webView = WKWebView(frame: .zero, configuration: webConfiguration)
                      webView.navigationDelegate = self
                      return webView
                  }()
              

              设置 WKProcessPool 是这里最重要的一步。 WKWebview 使用进程隔离 - 这意味着它运行在与您的应用进程不同的进程上。这有时会导致冲突并阻止您的 cookie 与 WKWebview 正确同步。

              现在我们来看看WKProcessPool的定义

              与 Web 视图关联的进程池由其 Web 视图配置指定。每个 Web 视图都有自己的 Web 内容进程,直到达到实现定义的进程限制;之后,具有相同进程池的 Web 视图最终共享 Web 内容进程。

              如果你打算对子序列请求使用相同的 WKWebview,请注意最后一句

              具有相同进程池的 Web 视图最终共享 Web 内容 进程

              我的意思是,如果您每次为同一个域配置 WKWebView 时不使用相同的 WKProcessPool 实例(也许您有一个包含 WKWebView 的 VC A 并且您想创建不同的 VC A 实例在不同的地方),可能会有冲突设置cookie。为了解决这个问题,在第一次为加载域 B 的 WKWebView 创建 WKProcessPool 之后,我将它保存在一个单例中,并且每次我必须创建一个加载相同域 B 的 WKWebView 时都使用相同的 WKProcessPool

              private lazy var mainWebView: WKWebView = {
                  let webConfiguration = WKWebViewConfiguration()
                  if Enviroment.shared.processPool == nil {
                      Enviroment.shared.processPool = WKProcessPool()
                  }
                  webConfiguration.processPool = Enviroment.shared.processPool!
                  webConfiguration.processPool = WKProcessPool()
                  let webView = WKWebView(frame: .zero, configuration: webConfiguration)
                  webView.navigationDelegate = self
                  return webView
              }()
              

              在初始化过程之后,您可以在httpCookieStore.setCookie 的完成块内加载一个URLRequest。在这里,您必须将 cookie 附加到请求标头,否则它将不起作用。

              P/s:我从 Dan Loewenherz 上面的精彩答案中偷了扩展

              mainWebView.configuration.websiteDataStore.httpCookieStore.setCookie(your_cookie) {
                      self.mainWebView.load(your_request, with: [your_cookie])
              }
              
              extension WKWebView {
                 func load(_ request: URLRequest, with cookies: [HTTPCookie]) {
                    var request = request
                    let headers = HTTPCookie.requestHeaderFields(with: cookies)
                    for (name, value) in headers {
                       request.addValue(value, forHTTPHeaderField: name)
                    }        
                    load(request)
                 }
              }
              

              【讨论】:

                【解决方案16】:

                添加多个 cookie 项时,您可以这样做:(每个项都需要path & domain

                NSString *cookie = [NSString stringWithFormat:@"document.cookie = 'p1=%@;path=/;domain=your.domain;';document.cookie = 'p2=%@;path=/;domain=your.domain;';document.cookie = 'p3=%@;path=/;domain=your.domain;';", p1_string, p2_string, p3_string];
                
                WKUserScript *cookieScript = [[WKUserScript alloc]
                            initWithSource:cookie
                            injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
                
                [userContentController addUserScript:cookieScript];
                

                否则,只会设置第一个 cookie 项。

                【讨论】:

                  【解决方案17】:

                  您还可以使用 WKWebsiteDataStore 从 UIWebView 获取与 HTTPCookieStorage 类似的行为。

                  let dataStore = WKWebsiteDataStore.default()
                  let cookies = HTTPCookieStorage.shared.cookies ?? [HTTPCookie]()
                  cookies.forEach({
                      dataStore.httpCookieStore.setCookie($0, completionHandler: nil)
                  })
                  

                  【讨论】:

                    【解决方案18】:

                    以下代码在我的项目 Swift5 中运行良好。 尝试通过下面的 WKWebView 加载 url:

                        private func loadURL(urlString: String) {
                            let url = URL(string: urlString)
                            guard let urlToLoad = url else { fatalError("Cannot find any URL") }
                    
                            // Cookies configuration
                            var urlRequest = URLRequest(url: urlToLoad)
                            if let cookies = HTTPCookieStorage.shared.cookies(for: urlToLoad) {
                                let headers = HTTPCookie.requestHeaderFields(with: cookies)
                                for header in headers { urlRequest.addValue(header.value, forHTTPHeaderField: header.key) }
                            }
                    
                            webview.load(urlRequest)
                        }
                    

                    【讨论】:

                      【解决方案19】:

                      这是我在 iOS 9 或更高版本中处理 Cookie 和 WKWebView 的解决方案。

                      import WebKit
                      
                      extension WebView {
                      
                          enum LayoutMode {
                              case fillContainer
                          }
                      
                          func autoLayout(_ view: UIView?, mode: WebView.LayoutMode = .fillContainer) {
                              guard let view = view else { return }
                              self.translatesAutoresizingMaskIntoConstraints = false
                              view.addSubview(self)
                      
                              switch mode {
                              case .fillContainer:
                                      NSLayoutConstraint.activate([
                                      self.topAnchor.constraint(equalTo: view.topAnchor),
                                      self.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                                      self.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                                      self.bottomAnchor.constraint(equalTo: view.bottomAnchor)
                                  ])
                              }
                          }
                      
                      }
                      
                      class WebView : WKWebView {
                      
                          var request : URLRequest?
                      
                          func load(url: URL, useSharedCookies: Bool = false) {
                              if useSharedCookies, let cookies = HTTPCookieStorage.shared.cookies(for: url) {
                                  self.load(url: url, withCookies: cookies)
                              } else {
                                  self.load(URLRequest(url: url))
                              }
                          }
                      
                          func load(url: URL, withCookies cookies: [HTTPCookie]) {
                              self.request = URLRequest(url: url)
                              let headers = HTTPCookie.requestHeaderFields(with: cookies)
                              self.request?.allHTTPHeaderFields = headers
                              self.load(request!)
                          }
                      
                      }
                      

                      【讨论】:

                        【解决方案20】:

                        我对 nteiss 的回答。在iOS 11, 12, 13 上测试。看起来您不必再在iOS 13 上使用DispatchGroup

                        我在WKWebViewConfiguration 上使用非静态函数includeCustomCookies,这样我每次创建新的WKWebViewConfiguration 时都可以更新cookies

                        extension WKWebViewConfiguration {
                            func includeCustomCookies(cookies: [HTTPCookie], completion: @escaping  () -> Void) {
                                let dataStore = WKWebsiteDataStore.nonPersistent()
                                let waitGroup = DispatchGroup()
                        
                                for cookie in cookies {
                                    waitGroup.enter()
                                    dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
                                }
                        
                                waitGroup.notify(queue: DispatchQueue.main) {
                                    self.websiteDataStore = dataStore
                                    completion()
                                }
                            }
                        }
                        

                        然后我这样使用它:

                        let customUserAgent: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15"
                        
                        let customCookies: [HTTPCookie] = {
                            let cookie1 = HTTPCookie(properties: [
                                .domain: "yourdomain.com",
                                .path: "/",
                                .name: "auth_token",
                                .value: APIManager.authToken
                            ])!
                        
                            let cookie2 = HTTPCookie(properties: [
                                .domain: "yourdomain.com",
                                .path: "/",
                                .name: "i18next",
                                .value: "ru"
                            ])!
                        
                            return [cookie1, cookie2]
                        }()
                        
                        override func viewDidLoad() {
                            super.viewDidLoad()
                        
                            activityIndicatorView.startAnimating()
                        
                            let webConfiguration = WKWebViewConfiguration()
                            webConfiguration.includeCustomCookies(cookies: customCookies, completion: { [weak self] in
                                guard let strongSelf = self else { return }
                                strongSelf.webView = WKWebView(frame: strongSelf.view.bounds, configuration: webConfiguration)
                                strongSelf.webView.customUserAgent = strongSelf.customUserAgent
                                strongSelf.webView.navigationDelegate = strongSelf
                                strongSelf.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
                                strongSelf.view.addSubview(strongSelf.webView)
                                strongSelf.view.bringSubviewToFront(strongSelf.activityIndicatorView)
                                strongSelf.webView.load(strongSelf.request)
                            })
                        }
                        

                        【讨论】:

                          【解决方案21】:

                          我犯的这个错误是我在域属性中传递了整个 url,它应该只是域名。

                          let cookie = HTTPCookie(properties: [
                          .domain: "example.com",
                          .path: "/",
                          .name: "MyCookieName",
                          .value: "MyCookieValue",
                          .secure: "TRUE",
                          ])! 
                          
                          webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
                          

                          【讨论】:

                            【解决方案22】:

                            iOS 10+ 解决方案

                            详情

                            • 斯威夫特 5.1
                            • Xcode 11.6 (11E708)

                            解决方案

                            import UIKit
                            import WebKit
                            extension WKWebViewConfiguration {
                                func set(cookies: [HTTPCookie], completion: (() -> Void)?) {
                                    if #available(iOS 11.0, *) {
                                        let waitGroup = DispatchGroup()
                                        for cookie in cookies {
                                            waitGroup.enter()
                                            websiteDataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
                                        }
                                        waitGroup.notify(queue: DispatchQueue.main) { completion?() }
                                    } else {
                                        cookies.forEach { HTTPCookieStorage.shared.setCookie($0) }
                                        self.createCookiesInjectionJS(cookies: cookies) {
                                            let script = WKUserScript(source: $0, injectionTime: .atDocumentStart, forMainFrameOnly: false)
                                            self.userContentController.addUserScript(script)
                                            DispatchQueue.main.async { completion?() }
                                        }
                                    }
                                }
                            
                                private func createCookiesInjectionJS (cookies: [HTTPCookie],  completion: ((String) -> Void)?) {
                                    var scripts: [String] = ["var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } )"]
                                    let now = Date()
                            
                                    for cookie in cookies {
                                        if let expiresDate = cookie.expiresDate, now.compare(expiresDate) == .orderedDescending { continue }
                                        scripts.append("if (cookieNames.indexOf('\(cookie.name)') == -1) { document.cookie='\(cookie.javaScriptString)'; }")
                                    }
                                    completion?(scripts.joined(separator: ";\n"))
                                }
                            }
                            
                            extension WKWebView {
                                func loadWithCookies(request: URLRequest) {
                                    if #available(iOS 11.0, *) {
                                        load(request)
                                    } else {
                                        var _request = request
                                        _request.setCookies()
                                        load(_request)
                                    }
                                }
                            }
                            
                            extension URLRequest {
                            
                                private static var cookieHeaderKey: String { "Cookie" }
                                private static var noAppliedcookieHeaderKey: String { "No-Applied-Cookies" }
                            
                                var hasCookies: Bool {
                                    let headerKeys = (allHTTPHeaderFields ?? [:]).keys
                                    var hasCookies = false
                                    if headerKeys.contains(URLRequest.cookieHeaderKey) { hasCookies = true }
                                    if !hasCookies && headerKeys.contains(URLRequest.noAppliedcookieHeaderKey) { hasCookies = true }
                                    return hasCookies
                                }
                            
                                mutating func setCookies() {
                                    if #available(iOS 11.0, *) { return }
                                    var cookiesApplied = false
                                    if let url = self.url, let cookies = HTTPCookieStorage.shared.cookies(for: url) {
                                        let headers = HTTPCookie.requestHeaderFields(with: cookies)
                                        for (name, value) in headers { setValue(value, forHTTPHeaderField: name) }
                                        cookiesApplied = allHTTPHeaderFields?.keys.contains(URLRequest.cookieHeaderKey) ?? false
                                    }
                                    if !cookiesApplied { setValue("true", forHTTPHeaderField: URLRequest.noAppliedcookieHeaderKey) }
                                }
                            }
                            
                            /// https://github.com/Kofktu/WKCookieWebView/blob/master/WKCookieWebView/WKCookieWebView.swift
                            extension HTTPCookie {
                            
                                var javaScriptString: String {
                                    if var properties = properties {
                                        properties.removeValue(forKey: .name)
                                        properties.removeValue(forKey: .value)
                            
                                        return properties.reduce(into: ["\(name)=\(value)"]) { result, property in
                                            result.append("\(property.key.rawValue)=\(property.value)")
                                        }.joined(separator: "; ")
                                    }
                            
                                    var script = [
                                        "\(name)=\(value)",
                                        "domain=\(domain)",
                                        "path=\(path)"
                                    ]
                            
                                    if isSecure { script.append("secure=true") }
                            
                                    if let expiresDate = expiresDate {
                                        script.append("expires=\(HTTPCookie.dateFormatter.string(from: expiresDate))")
                                    }
                            
                                    return script.joined(separator: "; ")
                                }
                            
                                private static let dateFormatter: DateFormatter = {
                                    let dateFormatter = DateFormatter()
                                    dateFormatter.locale = Locale(identifier: "en_US")
                                    dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
                                    dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
                                    return dateFormatter
                                }()
                            }
                            

                            用法

                            不要忘记在此处粘贴解决方案代码

                            class WebViewController: UIViewController {
                               
                                private let host = "google.com"
                                private weak var webView: WKWebView!
                            
                                override func viewDidLoad() {
                                    super.viewDidLoad()
                                    setupWebView()
                                }
                                
                                func setupWebView() {
                                    let cookies: [HTTPCookie] = []
                                    let configuration = WKWebViewConfiguration()
                                    configuration.websiteDataStore = .nonPersistent()
                                    configuration.set(cookies: cookies) {
                                        let webView = WKWebView(frame: .zero, configuration: configuration)
                                        /// ..
                                        self.webView = webView
                                        
                                        self.loadPage(url: URL(string:self.host)!)
                                    }
                                }
                                
                                private func loadPage(url: URL) {
                                    var request = URLRequest(url: url)
                                    request.setCookies()
                                    webView.load(request)
                                }
                            }
                            
                            extension WebViewController: WKNavigationDelegate {
                            
                                 // https://stackoverflow.com/a/47529039/4488252
                                 func webView(_ webView: WKWebView,
                                              decidePolicyFor navigationAction: WKNavigationAction,
                                              decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
                            
                                     if #available(iOS 11.0, *) {
                                         decisionHandler(.allow)
                                     } else {
                                         guard let url = navigationAction.request.url, let host = url.host, host.contains(self.host) else {
                                             decisionHandler(.allow)
                                             return
                                         }
                            
                                         if navigationAction.request.hasCookies {
                                             decisionHandler(.allow)
                                         } else {
                                             DispatchQueue.main.async {
                                                 decisionHandler(.cancel)
                                                 self.loadPage(url: url)
                                             }
                                         }
                                     }
                                 }
                             }
                            

                            完整样本

                            不要忘记在此处粘贴解决方案代码

                            import UIKit
                            import WebKit
                            
                            class ViewController: UIViewController {
                            
                                private weak var webView: WKWebView!
                                let url = URL(string: "your_url")!
                                
                                var cookiesData: [String : Any]  {
                                    [
                                        "access_token": "your_token"
                                    ]
                                }
                                
                                override func viewDidLoad() {
                                    super.viewDidLoad()
                                    let configuration = WKWebViewConfiguration()
                                    
                                    guard let host = self.url.host else { return }
                                    configuration.set(cookies: createCookies(host: host, parameters: self.cookiesData)) {
                                        let webView = WKWebView(frame: .zero, configuration: configuration)
                                        self.view.addSubview(webView)
                                        self.webView = webView
                                        webView.navigationDelegate = self
                                        webView.translatesAutoresizingMaskIntoConstraints = false
                                        webView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
                                        webView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
                                        self.view.bottomAnchor.constraint(equalTo: webView.bottomAnchor).isActive = true
                                        self.view.rightAnchor.constraint(equalTo: webView.rightAnchor).isActive = true
                            
                                        self.loadPage(url: self.url)
                                    }
                                }
                            
                                private func loadPage(url: URL) {
                                    var request = URLRequest(url: url)
                                    request.timeoutInterval = 30
                                    request.setCookies()
                                    webView.load(request)
                                }
                                
                                private func createCookies(host: String, parameters: [String: Any]) -> [HTTPCookie] {
                                    parameters.compactMap { (name, value) in
                                        HTTPCookie(properties: [
                                            .domain: host,
                                            .path: "/",
                                            .name: name,
                                            .value: "\(value)",
                                            .secure: "TRUE",
                                            .expires: Date(timeIntervalSinceNow: 31556952),
                                        ])
                                    }
                                }
                            }
                            
                            extension ViewController: WKNavigationDelegate {
                            
                                // https://stackoverflow.com/a/47529039/4488252
                                func webView(_ webView: WKWebView,
                                             decidePolicyFor navigationAction: WKNavigationAction,
                                             decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
                            
                                    if #available(iOS 11.0, *) {
                                        decisionHandler(.allow)
                                    } else {
                                        guard let url = navigationAction.request.url, let host = url.host, host.contains(self.url.host!) else {
                                            decisionHandler(.allow)
                                            return
                                        }
                            
                                        if navigationAction.request.hasCookies {
                                            decisionHandler(.allow)
                                        } else {
                                            DispatchQueue.main.async {
                                                decisionHandler(.cancel)
                                                self.loadPage(url: url)
                                            }
                                        }
                                    }
                                }
                            }
                            

                            Info.plist

                            添加 Info.plist 传输安全设置

                            <key>NSAppTransportSecurity</key>
                            <dict>
                                <key>NSAllowsArbitraryLoads</key>
                                <true/>
                            </dict>
                            

                            【讨论】:

                              【解决方案23】:

                              这是我的做法-

                              在 AppDelegate 的 didFinishLaunchingWithOptions 中(或创建 WebView 之前的任何地方)调用 initWebConfig,否则有时 Cookie 无法正确同步-

                                  func initWebConfig() {
                                      self.webConfig = WKWebViewConfiguration()
                                      self.webConfig.websiteDataStore = WKWebsiteDataStore.nonPersistent()
                                  }
                                         
                                  func setCookie(key: String, value: AnyObject, domain: String? = nil, group: DispatchGroup? = nil) {
                                                      
                                                      let cookieProps: [HTTPCookiePropertyKey : Any] = [
                                                          .domain: domain ?? "google.com",
                                                          .path: "/",
                                                          .name: key,
                                                          .value: value,
                                                      ]
                                                      
                                                      if let cookie = HTTPCookie(properties: cookieProps) {
                                                          group?.enter()
                                                          let webConfig = (UIApplication.shared.delegate as? AppDelegate)?.webConfig
                                        
                              webConfig?.websiteDataStore.httpCookieStore.setCookie(cookie) {
                                                              group?.leave()
                                                          }
                                                      }
                                                  }
                              

                              如果需要,在调度组中设置 cookie-

                               let group = DispatchGroup()
                                              self.setCookie(key: "uuid", value: "tempUdid" as AnyObject, group: group)
                                              self.setCookie(key: "name", value: "tempName" as AnyObject, group: group)
                                              
                                              group.notify(queue: DispatchQueue.main) {
                                                  //Create and Load WebView here 
                                                  let webConfig = (UIApplication.shared.delegate as? AppDelegate)?.webConfig ?? WKWebViewConfiguration()
                                                  //create urlRequest
                                                  let webView = WKWebView(frame: .zero, configuration: webConfig)
                                                  self.webView.load(urlRequest)
                                              }
                              

                              【讨论】:

                              • 我发现 nonPersistent 效果最好,不知道为什么。因为干净隔离的环境?
                              【解决方案24】:

                              终于得到了适用于 ios 11+ 的解决方案。在这里粘贴我的代码...

                              extension WKWebViewConfiguration {
                              
                              static func includeCookie(preferences:WKPreferences, completion: @escaping (WKWebViewConfiguration?) -> Void) {
                                  let config = WKWebViewConfiguration()
                                  
                                  guard let cookies = HTTPCookieStorage.shared.cookies else {
                                      completion(config)
                                      return
                                  }
                                  
                                  config.preferences = preferences
                                  let dataStore = WKWebsiteDataStore.nonPersistent()
                                  HTTPCookieStorage.shared.cookieAcceptPolicy = .always
                                  
                                  DispatchQueue.main.async {
                                      let waitGroup = DispatchGroup()
                                      
                                      for cookie in cookies{
                                          waitGroup.enter()
                                          let customCookie = HTTPCookie(properties: [
                                              .domain: cookie.domain,
                                              .path: cookie.path,
                                              .name: cookie.name,
                                              .value: cookie.value,
                                              .secure: cookie.isSecure,
                                              .expires: cookie.expiresDate ?? NSDate(timeIntervalSinceNow: 31556926)
                                          ])
                                          if let cookieData = customCookie{
                                              dataStore.httpCookieStore.setCookie(cookieData) {
                                                  waitGroup.leave()
                                              }
                                          }
                                      }
                                      
                                      waitGroup.notify(queue: DispatchQueue.main) {
                                          config.websiteDataStore = dataStore
                                          completion(config)
                                      }
                                  }
                              }
                              }
                              

                              在 WKWebViewConfiguration 中设置好 cookie 后,使用相同的配置加载 webview...

                              WKWebViewConfiguration.includeCookie(preferences: preferences, completion: {
                                          [weak self] config in
                                  if let `self` = self {
                                      if let configuration = config {
                                          webview = WKWebView(frame: self.contentView.bounds, configuration: config)
                                          webview.configuration.websiteDataStore.httpCookieStore.getAllCookies { (response) in
                                              print("")
                                          }
                              
                                          self.contentView.addSubview(webview)
                              
                                          if let filePath = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "packageDetailWevview") {
                              
                                              if let requestUrl = filePath {
                                                  let request = URLRequest(url: requestUrl)
                                                  webview.load(request)
                                              }
                                          }
                                      }
                                  }
                              })
                              

                              【讨论】:

                                猜你喜欢
                                • 2012-02-03
                                • 2014-12-22
                                • 2018-02-05
                                • 2012-01-16
                                • 2021-08-16
                                • 2011-05-26
                                • 2011-03-21
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多