Чи можу я встановити файли cookie, які використовуються WKWebView?


135

Я намагаюся переключити наявну програму з UIWebViewна WKWebView. Поточний додаток керує входом / сеансом користувачів поза межами webviewта встановлює cookiesнеобхідні для аутентифікації дані в NSHTTPCookieStore. На жаль , новий WKWebViewне використовує cookiesз NSHTTPCookieStorage. Чи є інший спосіб досягти цього?

Відповіді:


186

Редагувати лише для iOS 11+

Використовуйте WKHTTPCookieStore :

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

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

Оскільки ви перетягуєте їх з HTTPCookeStorage, ви можете зробити це:

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

Стара відповідь на iOS 10 і нижче

Якщо ви вимагаєте, щоб ваші файли cookie були встановлені в початковому запиті на завантаження, ви можете встановити їх на NSMutableURLRequest. Оскільки файли cookie є лише спеціально відформатованим заголовком запиту, цього можна досягти так:

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

Якщо вам потрібні наступні запити AJAX на сторінці, щоб встановити їх файли cookie, цього можна досягти, просто застосувавши WKUserScript для програмного встановлення значень через javascript при запуску документа так:

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

Поєднання цих двох методів повинно дати вам достатньо інструментів для передачі значень файлів cookie з Native App Land на веб-перегляд Web View. Додаткову інформацію можна знайти в API JavaScript для файлів cookie на сторінці Mozilla, якщо вам потрібні деякі більш вдосконалені файли cookie.

Так, це гарно, що Apple не підтримує багатьох приємностей UIWebView . Не впевнений, чи коли-небудь вони їх підтримають, але, сподіваємось, вони незабаром дістануть це. Сподіваюся, це допомагає!


1
Де найкраще ввести файли cookie для наступних запитів? Наприклад, початкове завантаження сторінки висвітлено у відповіді вище, але що робити, якщо на сторінці є посилання, які також призводять до того ж домену, а також потрібні ті самі файли cookie, які вводяться у запит? didStartProvisionalNavigation?
Мейсон Жвіті

1
вибачте, що це не працює для вас. На мій погляд, якщо домени однакові, не повинно виникнути жодних проблем. Чи можете ви двічі перевірити в коді, що посилання вказує на той самий домен, з якого ви завантажили запит? Також файли cookie можуть бути обмежені певним "контуром". Може, це викликає якісь проблеми?
маттр

11
Зауважте, що техніка javascript для встановлення файлів cookie не буде працювати для файлів cookie "Тільки HTTP".
Ахмед Насер

1
Вищеописаний метод працює чудово ... але я міг бачити файли cookie, що дублюються у наступних дзвінках AJAX (дублюється лише один раз).
Дурга Вундаваллі

1
@ Axel92Dev вирішенням завдання буде переконатися, що перший запит, зроблений з вашого веб-перегляду на ваш сервер, отримає відповідь, яка прямо повідомляє веб-перегляду встановити файли cookie знову за допомогою прапора HTTPOnly (тобто: встановити файли cookie у відповіді). Ви можете створити спеціальний API з цією єдиною метою під час ініціалізації веб-перегляду, а потім використовувати веб-перегляд зазвичай з успіхом.
Ахмед Насер

64

Погравши з цією відповіддю (яка була фантастично корисною :), нам довелося внести кілька змін:

  • Нам потрібні перегляди в Інтернеті для роботи з кількома доменами, не пропускаючи інформацію про приватне cookie між цими доменами
  • Нам це потрібно для вшанування безпечних файлів cookie
  • Якщо сервер змінює значення файлу cookie, ми хочемо, щоб наша програма знала про це в NSHTTPCookieStorage
  • Якщо сервер змінює значення файлу cookie, ми не хочемо, щоб наші сценарії повертали його до початкового значення, коли ви переходите за посиланням / AJAX тощо.

Тож ми змінили наш код таким, який був таким;

Створення запиту

NSMutableURLRequest *request = [originalRequest mutableCopy];
NSString *validDomain = request.URL.host;
const BOOL requestIsSecure = [request.URL.scheme isEqualToString:@"https"];

NSMutableArray *array = [NSMutableArray array];
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
    // Don't even bother with values containing a `'`
    if ([cookie.name rangeOfString:@"'"].location != NSNotFound) {
        NSLog(@"Skipping %@ because it contains a '", cookie.properties);
        continue;
    }

    // Is the cookie for current domain?
    if (![cookie.domain hasSuffix:validDomain]) {
        NSLog(@"Skipping %@ (because not %@)", cookie.properties, validDomain);
        continue;
    }

    // Are we secure only?
    if (cookie.secure && !requestIsSecure) {
        NSLog(@"Skipping %@ (because %@ not secure)", cookie.properties, request.URL.absoluteString);
        continue;
    }

    NSString *value = [NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value];
    [array addObject:value];
}

NSString *header = [array componentsJoinedByString:@";"];
[request setValue:header forHTTPHeaderField:@"Cookie"];

// Now perform the request...

Це гарантує, що для першого запиту встановлено правильний файл cookie, не надсилаючи файлів cookie із спільного сховища для інших доменів і не надсилаючи захищених файлів cookie у незахищений запит.

Справа з подальшими запитами

Нам також потрібно переконатися, що в інших запитах встановлено файли cookie. Це робиться за допомогою скрипту, який працює при завантаженні документа, який перевіряє, чи є набір файлів cookie, а якщо ні, встановіть його на значення в NSHTTPCookieStorage.

// Get the currently set cookie names in javascriptland
[script appendString:@"var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );\n"];

for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
    // Skip cookies that will break our script
    if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
        continue;
    }

    // Create a line that appends this cookie to the web view's document's cookies
    [script appendFormat:@"if (cookieNames.indexOf('%@') == -1) { document.cookie='%@'; };\n", cookie.name, cookie.wn_javascriptString];
}

WKUserContentController *userContentController = [[WKUserContentController alloc] init];
WKUserScript *cookieInScript = [[WKUserScript alloc] initWithSource:script
                                                      injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                   forMainFrameOnly:NO];
[userContentController addUserScript:cookieInScript];

...

// Create a config out of that userContentController and specify it when we create our web view.
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = userContentController;

self.webView = [[WKWebView alloc] initWithFrame:webView.bounds configuration:config];

Справа зі змінами файлів cookie

Нам також потрібно мати справу з тим, як сервер змінює значення файлу cookie. Це означає додати ще один сценарій для виклику з веб-перегляду, який ми створюємо для оновлення NSHTTPCookieStorage.

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

[userContentController addScriptMessageHandler:webView
                                          name:@"updateCookies"];

і реалізувати метод делегата для оновлення будь-яких файлів cookie, які були змінені, переконуючись, що ми лише оновлюємо файли cookie з поточного домену!

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSArray<NSString *> *cookies = [message.body componentsSeparatedByString:@"; "];
    for (NSString *cookie in cookies) {
        // Get this cookie's name and value
        NSArray<NSString *> *comps = [cookie componentsSeparatedByString:@"="];
        if (comps.count < 2) {
            continue;
        }

        // Get the cookie in shared storage with that name
        NSHTTPCookie *localCookie = nil;
        for (NSHTTPCookie *c in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:self.wk_webView.URL]) {
            if ([c.name isEqualToString:comps[0]]) {
                localCookie = c;
                break;
            }
        }

        // If there is a cookie with a stale value, update it now.
        if (localCookie) {
            NSMutableDictionary *props = [localCookie.properties mutableCopy];
            props[NSHTTPCookieValue] = comps[1];
            NSHTTPCookie *updatedCookie = [NSHTTPCookie cookieWithProperties:props];
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:updatedCookie];
        }
    }
}

Це, здається, виправляє наші проблеми із файлами cookie, не маючи нам справу з кожним місцем, де ми використовуємо WKWebView по-різному. Зараз ми можемо просто використовувати цей код як помічник для створення наших переглядів в Інтернеті, і він прозоро оновлює NSHTTPCookieStorageдля нас.


EDIT: Виявляється, я використовував приватну категорію в 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;
}

6
Я загорнув ваш код у підклас WKWebView. Не соромтеся перевірити це github.com/haifengkao/YWebView
Hai Feng Kao

Що робити, якщо ваші файли cookie містять = знаки у значенні? Це би спрацювало?
iOSAddicted

@iOSAddicted Я так думаю . Якщо ваше значення було a=bб, ви закінчилися рядом файлів cookie name=a=b;domain=.example.com;path=/- я вважаю, що стандарт розбивається, ;а потім розпадається на першому = в парі key = value. Я б протестував це, хоча :)
deanWombourne

Ваша відповідь мені дуже допомогла, проте я хотів би додати щось до Вашої публікації, є кілька ризиків при використанні Вашого методу оновлення, деякі рамки JS можуть створити файли cookie з однаковим іменем, але інший домен, і якщо Ви спробуєте оновити його використовуючи методи js, у вас є високий ризик оновлення файлу cookie з неправильним значенням. Також для нас, рядок файлів cookie js, потрібно було позбавити захищеного прапора, оскільки наш сервер робить неприємні переадресації між http та https, внаслідок чого захищені файли cookie відсутні на деяких сторінках у деяких неприємних крайових випадках.
РікардоДуарте

Насправді я думаю, що компанія, з якою я був, коли писав це, мусила додати до неї деякий захист домену після того, як вона вийшла в реальність. Ми ніколи (afaik) не стикалися з безпечною / незахищеною проблемою - це звучить як кошмар!
deanWombourne

42

Файли 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. Отже, якщо файли cookie сеансу оновлюються у веб-перегляді, ви можете вручну оновити систему за HTTPCookieStorageдопомогою цього нового файлу cookie, якщо потрібно.

Щоб дізнатися більше про це, пропустіть до 18:00 на цьому веб-сайті WWDC сесії на замовлення веб-контенту . На початку цього сеансу є оманливий зразок коду, який опускає той факт, що веб-перегляд повинен бути створений в обробнику завершення.

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

Демонстрація в прямому ефірі о 18:00 це пояснює.

Редагувати Щодо Mojave Beta 7 та iOS 12 Beta 7 як мінімум, я бачу набагато більш послідовну поведінку з файлами cookie. Цей setCookie(_:)метод навіть дозволяє встановити файли cookie після WKWebViewстворення. Я знайшов це важливо , хоча, щоб НЕ доторкнутися до processPoolзмінної взагалі. Функціонал налаштування файлів cookie найкраще працює, коли не створюються додаткові пули та коли це властивість залишається в спокої. Я думаю, що можна впевнено сказати, що у нас виникли проблеми через деякі помилки в WebKit.


Схоже, обробка та налаштування файлів cookie надійніші в Mojave 10.14 beta 3 та iOS 12 beta 3
nteissler

6
Дуже глибока і недооцінена відповідь
Ніколас Карраско

1
У мене все ще виникає проблема в iOS 12 з уже завантаженим WKWebView. Іноді setCookie () насправді буде синхронізовано з WKWebView одразу, іноді це не зробить обробку дещо спорадичною
bmjohns

Я все ще не бачив проблем, оскільки радіолокаційний апарат був зафіксований, але набагато рідше. Як часто ви бачите помилку файлів cookie? Якщо у вас є відтворюваний, досить маленький проект, я б дуже рекомендував надіслати помилку на веб-касах тут: webkit.org/reporting-bugs Ви також можете твітувати Брейді Ейдсона (добре) архітектора веб-какет у Apple, який дуже чуйно реагує на ці види звіти та помилки.
nteissler

це правильна відповідь - немає необхідності вручну перекладати файли cookie як поля заголовка в кожному URLRequest, це просто те, що setCookie () потрібно використовувати, як описано тут.
Гійом Лоран

25

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

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

Дивовижний хак, особливо те, наскільки непримітний iOS полягає в тому, щоб перекрити файл cookie в існуючому WKWebView. Єдине, що попередній WKNavigationKey став несвіжим. Інший код може даремно чекати на старий.
BaseZen

2
це правильно? Оцініть, що це може спрацювати за деяких обставин. Однак відповідальність цього методу делегата - рішенняPolicyForNavigationAction - полягає у вирішенні політики; фактично не завантажувати запит. Це було ініційовано раніше. У цьому випадку це не спричиняє завантаження запиту двічі?
Макс Маклауд

2
@MaxMacLeod У elseумові, з якого він викликає decisionHandlerзакриття, .cancelтому webviewвін фактично не завантажує початковий запит. Після loadRequestвиклику в elseумові цей метод делегата буде викликаний знову для цього запиту, і він перейде в ifумову, оскільки Cookieзаголовок буде присутній.
halil_g

2
Хоча це не спрацює, коли в початковому запиті вже встановлено деякі файли cookie, оскільки він ніколи не перейде в elseумову.
halil_g

Зауважте, що це 1) Не працює для кожної ситуації - наприклад, коли веб-перегляд завантажує кадри 2) Небезпечно - він може надсилати файли cookie з конфіденційною інформацією на адресу третьої сторони
Петро Прокоп

20

Ось моя версія рішення 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
}

краще додати цей рядок, щоб переконатися у правильності форматування локалі:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu

куди це зателефонувати?
markhorrocks

Це добре спрацювало для мене в Swift 4 (з незначними налаштуваннями)
Frédéric Adda

Це чудово працює для мене, але лише вдруге, коли я відвідую сайт (перший раз файли cookie не встановлені) - хтось стикається з цим?
MrChrisBarker

Перше навантаження дасть помилку, друга робота навантаження: (що може бути проблемою?
Шаукет Шейх

10

встановити печиво

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

Оновлення 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)
    }
}

1
Привіт, ви можете додати код, щоб отримати файли cookie HTTPCookieStorage.shared?
markhorrocks

Це єдиний спосіб, коли я отримав WKWebView додати файли cookie до кожного запиту, зробленого веб-переглядом
Chicowitz

Якщо він містить https cookie у відповідь, ви не зможете отримати значення файлів cookie таким чином.
мозок

1
це робиться лише встановленим файлом cookie назад у сховище httpcookies, де код, який встановлює файли cookie для wkwebview?
Шейкет Шейх

8

Переглянувши різні відповіді тут і не маючи успіху, я прочесав документацію WebKit і натрапив на requestHeaderFieldsстатичний метод HTTPCookie, який перетворює масив файлів cookie у формат, придатний для поля заголовка. Поєднавши це з розумінням 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, якщо ви просто хочете отримати рішення, що випадає. Ура!


1
@ShauketSheikh Хм, у яких ситуаціях це не працює?
Dan Loewenherz

Я протестував за допомогою симулятора ios 8, схоже, не надсилає файли cookie. я двічі перевірив це.
Шейкет Шейх

я опублікував свою відповідь, ви можете спробувати @Dan
Shauket Sheikh,

7

У iOS 11 ви можете керувати файлами cookie зараз :), дивіться цей сеанс: https://developer.apple.com/videos/play/wwdc2017/220/

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


2
@ShobhakarTiwari чому? чи відбуваються якісь зміни в офіційному випуску iOS11?
Джекі

Найкращий шлях, якщо ви підтримуєте лише iOS 11 і новіші версії, якщо вам потрібно підтримувати попередні версії, використовуйте JavaScript перед завантаженням сторінки.
ПашаN

Це працює для мене, за винятком того, що іноді метод setcookie НЕ запускає обробник його завершення, тобто іноді моя веб-сторінка не завантажується - відбувається лише на пристрої, трапляється 3-й / 4-й / 5-й раз закриття та повторне відкриття веб-перегляд, і після того, як це трапляється один раз, це продовжується, поки я не скидаю додаток - хтось також натрапляє на це?
Бінья Коатц

5

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

Для iOS> = 11.0 - Swift 4.2

Отримайте файли cookie http та встановіть у магазині файлів cookie wkwebview так, це дуже складний момент завантажувати ваш запит у wkwebview , потрібно надіслати запит на завантаження, коли файли cookie будуть встановлені повністю, ось функція, яку я написав.

Функція виклику із завершенням завершення виклику завантажує веб-перегляд. FYI ця функція обробляє лише 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

вам потрібно налаштувати кілька додаткових речей, які потрібно встановити двома часовими файлами cookie за допомогою WKUserScript і не забудьте додати файли cookie також у запиті, інакше файл cookie не синхронізується вперше, і ви побачите, що сторінка не завантажується належним чином. це чорт, який я знайшов, щоб підтримувати файли cookie для 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 одразу, є багато чортів для завантаження першої сторінки часу із файлом cookie. Це знову завантажити веб-перегляд, якщо він завершить процес, але я не рекомендую його використовувати, це не добре для точки зору користувача , чортів, кожен раз, коли ви готові завантажити файли 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


2

Будь ласка, знайдіть рішення, яке, швидше за все, спрацює для вас, поза коробкою. В основному це змінено і оновлено для Swift 4 @ user3589213 «s відповіді .

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

1

Я спробував усі відповіді вище, але жодна з них не працює. Після стількох спроб я нарешті знайшов надійний спосіб встановити cookie WKWebview.

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

П / с: Я вкрав розширення з фантастичної відповіді, викладеної Даном Ловенхерцом

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

1

Моя версія відповіді 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)
    })
}

0

Краще виправлення для запитів 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)
}

0

Якщо хтось використовує 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)
  }

0

Це працює для мене: Після встановлення певних файлів додайте записи fetchdata

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

                    })
                }

0

Додаючи помножені елементи файлів 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.


0

Ви також можете використовувати WKWebsiteDataStore, щоб отримати схожу поведінку з HTTPCookieStorage від UIWebView.

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

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

0

Це моє рішення для роботи з Cookies та 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!)
    }

}

0

Ця помилка, яку я робив, це те, що я передав цілу URL-адресу в атрибуті домену, це повинно бути лише доменне ім’я.

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

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.