Могу ли я установить файлы cookie, которые будут использоваться WKWebView?

Я пытаюсь переключить существующее приложение с UIWebView на WKWebView. Текущее приложение управляет входом / сеансом пользователей вне веб-представления и устанавливает файлы cookie, необходимые для аутентификации, в NSHTTPCookieStore. К сожалению, новый WKWebView не использует куки из NSHTTPCookieStorage. Есть ли другой способ добиться этого?

25 ответов

Решение

Редактировать только для iOS 11+

Используйте WKHTTCookieStore:

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 и ниже

Если вам требуется, чтобы ваши куки были установлены при первоначальном запросе загрузки, вы можете установить их в 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 javascript api на странице Mozilla, если вам нужны более продвинутые куки.

Да, это отстой, что Apple не поддерживает многие тонкости UIWebView. Не уверен, будут ли они когда-либо поддерживать их, но, надеюсь, они скоро доберутся до этого. Надеюсь это поможет!

После игры с этим ответом (который был фантастически полезен:) нам пришлось внести несколько изменений:

  • Нам нужны веб-представления для работы с несколькими доменами без утечки частной информации 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

Нам также нужно иметь дело с сервером, меняющим значение куки. Это означает добавление другого скрипта для обратного вызова из веб-представления, которое мы создаем для обновления нашего 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. Теперь мы можем просто использовать этот код в качестве помощника для создания наших веб-просмотров, и он прозрачно обновляется 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;
}

Файлы cookie должны быть установлены в конфигурации до WKWebView создано. В противном случае, даже с WKHTTPCookieStore "s setCookie обработчик завершения, куки не будут надежно синхронизированы с веб-просмотром. Это восходит к этой строке из документов на WKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

Тот @NSCopying является чем-то вроде глубокой копии. Реализация выше моего понимания, но конечный результат заключается в том, что если вы не установите файлы 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
    }
}

Приведенный выше пример откладывает создание представления до последнего возможного момента, другим решением было бы заранее создать конфигурацию или веб-представление и обработать асинхронную природу перед созданием контроллера представления.

Последнее замечание: как только вы создали это веб-представление, вы оставили его в свободном доступе, вы не можете добавить больше файлов cookie без использования методов, описанных в этом ответе. Однако вы можете использовать WKHTTPCookieStoreObserver API, по крайней мере, наблюдать изменения, происходящие с куки. Таким образом, если файл cookie сеанса обновляется в веб-просмотре, вы можете вручную обновить систему HTTPCookieStorage с этим новым файлом cookie, если это необходимо.

Для получения дополнительной информации перейдите к 18:00 на этой веб-загрузке пользовательского веб-контента 2017 года. В начале этого сеанса есть пример вводящего в заблуждение кода, в котором пропускается тот факт, что веб-просмотр должен быть создан в обработчике завершения.

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

Живая демонстрация в 18:00 разъясняет это.

По крайней мере, начиная с Mojave Beta 7 и iOS 12 Beta 7, я вижу гораздо более согласованное поведение с файлами cookie. setCookie(_:) появляется даже способ разрешить установку файлов cookie после WKWebView был создан. Я считаю важным, чтобы не трогать processPool переменная на всех. Функциональность настройки cookie работает лучше всего, когда не создаются дополнительные пулы и когда это свойство оставлено в покое. Я думаю, можно с уверенностью сказать, что у нас были проблемы из-за некоторых ошибок в WebKit.

Работать на меня

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

Вот моя версия решения Mattrs в Swift для внедрения всех файлов cookie из HTTPCookieStorage. Это было сделано главным образом для добавления файла 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
}

Установить cookie

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

удалить куки

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

В iOS 11 вы можете управлять cookie сейчас:), смотрите эту сессию: https://developer.apple.com/videos/play/wwdc2017/220/

введите описание изображения здесь

Посмотрев здесь различные ответы и не добившись успеха, я просмотрел документацию по WebKit и наткнулся на requestHeaderFields статический метод на HTTPCookie, который преобразует массив файлов cookie в формат, подходящий для поля заголовка. Объединяя это с пониманием Мэтра обновления URLRequest перед загрузкой его с заголовками печенья я прошел финишную черту.

Swift 4.1:

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, если вам просто нужно решение для встраивания. Ура!

Причина написанного ответа в том, что я пробовал много решений, но не работает должным образом. Большинство ответов не работает с первого раза. Пожалуйста, используйте мое решение, оно работает как для iOS более 11.0 и меньше, чем iOS 11 до 8.0

Для iOS >= 11.0 - Swift 4.2

Получите http-куки и установите их в wkwebview cookie store таким образом, очень сложно загрузить ваш запрос в wkwebview, нужно отправить запрос на загрузку, когда куки будут полностью установлены, вот функция, которую я написал.

Вызов функции с закрытием в завершении вы вызываете загрузку веб-просмотра. К вашему сведению, эта функция обрабатывает только 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, и не забудьте также добавить куки-файлы в запрос, иначе ваш куки-файл не синхронизируется в первый раз, и вы увидите, что ваша страница не загружается в первый раз должным образом. это черт возьми, я нашел, чтобы поддержать куки для iOS 8.0

перед вами 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 с первого раза. Один из них - перезагрузить веб-просмотр снова, если он завершает процесс, но я не рекомендую его использовать, это не очень хорошо для точки зрения пользователя. Черт возьми, всякий раз, когда вы готовы загрузить куки-файлы с установленным запросом в заголовок запроса, также не забудьте добавить проверку версии 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

Решение для iOS 10+

Детали

  • 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://stackru.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://stackru.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>

Эта ошибка, которую я делал, заключалась в том, что я передавал весь URL-адрес в атрибуте домена, это должно быть только имя домена.

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

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

Моя версия ответа nteiss. Проверено наiOS 11, 12, 13. Похоже, вам не нужно использоватьDispatchGroup на iOS 13 больше.

Я использую нестатическую функцию includeCustomCookies на WKWebViewConfiguration, чтобы я мог обновить cookies каждый раз я создаю новый WKWebViewConfiguration.

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

Пожалуйста, найдите решение, которое, скорее всего, будет работать для вас из коробки. В основном это модифицировано и обновлено для ответа 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.

Сначала вы должны создать экземпляр 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

Пул процессов, связанный с веб-представлением, определяется его конфигурацией веб-представления. Каждому веб-представлению присваивается собственный процесс веб-контента до тех пор, пока не будет достигнут предел процесса, определенный реализацией; после этого веб-представления с одним и тем же пулом процессов в конечном итоге обмениваются процессами веб-содержимого.

Обратите внимание на последнее предложение, если вы планируете использовать тот же WKWebview для запросов подпоследовательности.

веб-представления с одним и тем же пулом процессов в конечном итоге обмениваются процессами веб-содержимого

Я имею в виду, что если вы не используете один и тот же экземпляр WKProcessPool каждый раз, когда настраиваете WKWebView для одного и того же домена (возможно, у вас есть VC A, который содержит WKWebView, и вы хотите создать разные экземпляры VC A в разных местах), могут быть конфликты настроек куки. Чтобы решить эту проблему, после первого создания WKProcessPool для WKWebView, который загружает домен B, я сохраняю его в виде одиночного файла и использую тот же WKProcessPool каждый раз, когда мне нужно создать WKWebView, который загружает тот же домен B

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

После процесса инициализации вы можете загрузить URLRequest внутри блока завершения httpCookieStore.setCookie, Здесь вы должны прикрепить cookie к заголовку запроса, иначе он не будет работать.

P / s: Я украл расширение из фантастического ответа Дана Лёвенхерца, приведенного выше.

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

наконец-то заработало в 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)
  }


}

Наконец-то получил решение, которое работает на 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)
        }
    }
}

}

После установки файла cookie в WKWebViewConfiguration используйте ту же конфигурацию для загрузки веб-просмотра ...

WKWebViewConfiguration.includeCookie (предпочтения: предпочтения, завершение: конфигурация {[слабого себя], если разрешено self = self {if let configuration = config {webview = WKWebView (frame: self.contentView.bounds, configuration: config)webview.configuration.websiteDataStore.httpCookieStore.getAllCookies {(respone) 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)
        }
    }
            }
        }
    })

Вот как я это делаю -

вызовите initWebConfig в didFinishLaunchingWithOptions из AppDelegate (или где угодно перед созданием WebView), в противном случае иногда файлы 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)
                }

Лучшее исправление для запросов 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)
}

Это работает для меня: после 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))
                        }

                    })
                }

Код ниже хорошо работает в моем проекте Swift5. попробуйте загрузить URL-адрес с помощью 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)
    }

При добавлении нескольких файлов 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.

Если кто-то использует 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)
  }

Вы также можете использовать WKWebsiteDataStore, чтобы получить поведение, аналогичное HTTPCookieStorage, из UIWebView.

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

Это мое решение для обработки файлов cookie и WKWebView в iOS 9 или новее.

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

}
Другие вопросы по тегам