WKWebView 是否可以设置要使用到的 cookies?

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

195878 次浏览

仅适用于 iOS11以上版本

使用 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)
}

IOS10及以下版本的老版本

如果需要在初始加载请求上设置 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 请求,只需使用 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 值从 NativeAppLand 传输到 WebViewLand。如果你需要更高级的 cookie,你可以在 Mozilla 页面上的 Cookie javascript API上找到更多的信息。

是的,苹果公司不支持 UIWebView 的细节的很多产品真是糟透了。不确定他们是否会支持他们,但是希望他们很快就能做到这一点。希望这个能帮上忙!

为我工作

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)
}
}

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

  • 我们需要 Web 视图来处理多个域,而不会在这些域之间泄露私有 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 值的问题。这意味着添加另一个脚本来调用我们正在创建的 Web 视图来更新我们的 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;
}

这是我在 Swift 中的 事务解决方案,用于从 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
}

烤饼干

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()
}

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)
}
}

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

enter image description here

请找到最有可能为您工作的解决方案开箱即用。基本上,它的修改和更新为 Swift 4 @ user3589213回答

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)
}
}

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

@NSCopying var configuration: WKWebViewConfiguration { get }

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

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,您就把它放到了野外,如果不使用 这个答案中描述的方法,就不能添加更多 cookie。但是,您可以使用 WKHTTPCookieStoreObserver API 至少观察 Cookie 发生的变化。因此,如果一个会话 cookie 在 webview 中得到了更新,如果需要的话,您可以使用这个新 cookie 手动更新系统的 HTTPCookieStorage

欲了解更多内容,请跳到 2017年 WWDC 会议自定义 Web 内容加载的18:00。在这个会话的开始,有一个欺骗性的代码示例,它忽略了 webview 应该在完成处理程序中创建的事实。

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

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

编辑 至少在 Mojave Beta 7和 iOS 12 Beta 7中,我发现 cookie 的行为更加一致。setCookie(_:)方法甚至似乎允许在创建 WKWebView之后设置 cookie。我发现它很重要,但 不要碰processPool变量在所有。Cookie 设置功能在没有创建额外的池以及不使用该属性的情况下工作得最好。我认为可以肯定地说,由于 WebKit 中的一些 bug,我们遇到了一些问题。

对 XHR 请求的更好修复显示为 给你

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)
}

在浏览了这里的各种答案并且没有得到任何成功之后,我梳理了 WebKit 文档,偶然发现了 HTTPCookie上的 requestHeaderFields静态方法,它将 cookie 数组转换为适合头字段的格式。结合 Matt 的洞察力在加载之前更新 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)

这个扩展也可在 狮心扩展,如果你只是想一个下拉式解决方案。干杯!

发布这个答案的原因是,我尝试了很多解决方案,但是没有一个能正常工作,大多数答案不能正常工作,如果第一次必须设置 cookie,并且得到的结果 cookie 不能同步第一次,请使用这个解决方案它工作在 iOS > = 11.0 < = iOS 11直到8.0,也工作在 cookie 同步的第一次。

IOS > = 11.0 —— Swift 4.2

获取 Http://cookies/并像这样设置在 Wkwebview cookie 存储中,在 Wkwebview中加载请求是非常棘手的一点,当 cookie 完全设置时必须发送加载请求,下面是我写的函数。

在完成时用 了结调用函数,你可以调用 load 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()
}
}

从 iOS8到 iOS11

你需要设置一些额外的东西,你需要设置两个时间曲奇一通过使用 WKUserScript,不要忘记添加曲奇在请求,以及,否则你的曲奇没有同步第一次,你会看到你的页面没有加载第一次正确。这就是我发现的支持 iOS8.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,但是我不建议使用它,这对用户的观点不好,Heck 是当你准备加载请求集 cookie 在请求头中时,像这样,不要忘记加入 iOS 版本检查。在加载请求之前调用此函数。

request?.addCookies()

我写了 请求的分机号

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

如果有人在使用 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)
}

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

   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))
}


})
}

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

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

    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 Content 过程,直到达到实现定义的过程限制; 之后,具有相同过程池的 Web 视图最终共享 Web Content 过程。

如果您打算对后续请求使用相同的 WKWebview,请注意最后一句

具有相同流程池的 Web 视图最终会共享 Web Content 程序

我的意思是,如果每次为同一个域配置 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)
}
}

当添加多个 cookie 项目时,您可以这样做: (每个项目都需要 pathdomain)

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 项。

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

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

下面的代码在我的 Swift5项目中运行良好。 WKWebView 尝试加载网址如下:

    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)
}

这是我在 iOS9或更高版本中处理 Cookies 和 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!)
}


}

我的版本的奈斯的答案。在 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)
})
}

这个错误我正在做的是我传递整个网址在域属性,它应该只是域名。

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


webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)

IOS10 + 的解决方案

细节

  • Swift 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>

我是这么做的

在 did FinishLaunchingWithOptions of AppDelegate (或在创建 WebView 之前的任何地方)调用 initWebConfig,否则有时 Cookies 不能正确同步-

    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)
}

最后得到了在 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)
}
}
}
}
})

终于在 Swift 5中成功了。


extension WebController{


func save_cookies(){
let cookieStore = self.webView.configuration.websiteDataStore.httpCookieStore
cookieStore.getAllCookies { (cookies) in
let array = cookies.compactMap { (cookie) -> [HTTPCookiePropertyKey: Any]? in
cookie.properties
}
UserDefaults.standard.set(array, forKey: "cookies")


}
}


func load_cookies(){
// get status from cookies
// cookies are pre-installed from native code.
guard let cookies = UserDefaults.standard.value(forKey: "cookies") as? [[HTTPCookiePropertyKey: Any]] else {
return
}
cookies.forEach { (cookie) in
guard let cookie = HTTPCookie(properties: cookie ) else{return}
let cookieStore = self.webView.configuration.websiteDataStore.httpCookieStore
cookieStore.setCookie(cookie, completionHandler: nil)
}


webView.evaluateJavaScript("checkcookie_delay_1second()", completionHandler: nil)
}




}