Подтвердить что ты не робот

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

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

4b9b3361

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

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

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

Ответ 2

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

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

Ответ 3

работа для меня

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

Ответ 4

WKWebView cookie должны быть установлены в конфигурации до создания WKWebView. В противном случае, даже с WKHTTPCookieStore завершения WKHTTPCookieStore setCookie, файлы cookie не будут надежно синхронизированы с веб-представлением. Это восходит к этой строке из документов на 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 без использования методов, описанных в этом ответе. Однако вы можете использовать API- WKHTTPCookieStoreObserver чтобы хотя бы наблюдать изменения, происходящие с файлами cookie. Таким образом, если файл cookie сеанса обновляется в веб-просмотре, вы можете вручную обновить систему HTTPCookieStorage с помощью этого нового файла cookie, если это необходимо.

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

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

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

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

Ответ 5

Вот моя версия решения 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
}

Ответ 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)
    }
}

Ответ 7

установить файл 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()
}

Ответ 9

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

Swift 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, если вы просто хотите добавить решение. Ура!

Ответ 10

Причиной публикации этого ответа является то, что я пробовал множество решений, но никто не работает должным образом, большая часть ответа не работает в случае, когда необходимо установить cookie в первый раз, и полученный файл cookie не синхронизируется в первый раз. Используйте это решение, оно работает для обоих iOS> = 11.0 <= iOS 11 до 8.0, также работает с синхронизацией cookie в первый раз.

Для 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

Ответ 11

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

Ответ 12

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

Ответ 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 и установить его в 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)
   }
}

Ответ 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, чтобы получить поведение, аналогичное HTTPCookieStorage, из UIWebView.

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