Czy mogę ustawić pliki cookie, które mają być używane przez WKWebView?

144

Próbuję zmienić istniejącą aplikację z UIWebViewna WKWebView. Bieżąca aplikacja zarządza logowaniem / sesją użytkowników poza webviewdomeną i ustawia cookieswymagane uwierzytelnianie w NSHTTPCookieStore. Niestety nowy WKWebViewnie używa cookiesz NSHTTPCookieStorage. Czy jest inny sposób, aby to osiągnąć?

Przełęcz
źródło

Odpowiedzi:

196

Edytuj tylko dla iOS 11+

Użyj 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)

Ponieważ pobierasz je z HTTPCookeStorage, możesz to zrobić:

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

Stara odpowiedź na iOS 10 i starsze

Jeśli chcesz, aby pliki cookie były ustawione przy początkowym żądaniu ładowania, możesz ustawić je w NSMutableURLRequest. Ponieważ pliki cookie to tylko specjalnie sformatowany nagłówek żądania, można to osiągnąć w następujący sposób:

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];

Jeśli chcesz, aby kolejne żądania AJAX na stronie miały ustawione pliki cookie, można to osiągnąć, po prostu używając WKUserScript, aby ustawić wartości programowo za pomocą javascript na początku dokumentu:

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];

Połączenie tych dwóch technik powinno zapewnić wystarczające narzędzia do przenoszenia wartości plików cookie z Native App Land do Web View Land. Możesz znaleźć więcej informacji na temat API plików cookie javascript na stronie Mozilli, jeśli potrzebujesz bardziej zaawansowanych plików cookie.

Tak, to jest do bani, że Apple nie obsługuje wielu subtelności UIWebView . Nie jestem pewien, czy kiedykolwiek będą ich wspierać, ale miejmy nadzieję, że wkrótce się z tym pogodzą. Mam nadzieję że to pomoże!

mattr
źródło
1
Gdzie jest najlepsze miejsce do wstrzykiwania plików cookie na potrzeby kolejnych żądań? Np. Początkowe ładowanie strony jest omówione w powyższej odpowiedzi, ale co zrobić, jeśli na stronie znajdują się linki, które również prowadzą do tej samej domeny i również wymagają wstawienia tych samych plików cookie do żądania? didStartProvisionalNavigation?
Mason G. Zhwiti
1
przepraszam, że to nie działa. Myślę, że dopóki domeny są takie same, nie powinno być problemu. Czy możesz dwukrotnie sprawdzić w kodzie, że link prowadzi do tej samej domeny, z której załadowano żądanie? Pliki cookie można również ograniczyć do określonej „ścieżki”. Może to powoduje pewne problemy?
mattr
11
Należy pamiętać, że technika JavaScript służąca do ustawiania plików cookie nie działa w przypadku plików cookie typu „tylko HTTP”.
Ahmed Nasser
1
Powyższa metoda działa świetnie ... ale widziałem pliki cookie powielane w kolejnych wywołaniach AJAX (zduplikowane tylko raz).
Durga Vundavalli,
1
@ Axel92Dev obejściem byłoby upewnienie się, że pierwsze żądanie wysłane z twojego webview do twojego serwera otrzyma odpowiedź, która wyraźnie mówi webview, aby ponownie ustawił pliki cookie z flagą HTTPOnly (tj .: ustaw pliki cookie ponownie w odpowiedzi). Możesz utworzyć specjalny interfejs API wyłącznie do tego celu podczas inicjowania widoku internetowego, a następnie normalnie korzystać z widoku internetowego w przypadku sukcesu.
Ahmed Nasser
65

Po zabawie z tą odpowiedzią (która była fantastycznie pomocna :) musieliśmy wprowadzić kilka zmian:

  • Potrzebujemy widoków internetowych, aby obsługiwać wiele domen bez wycieku prywatnych informacji cookie między tymi domenami
  • Potrzebujemy tego do honorowania bezpiecznych plików cookie
  • Jeśli serwer zmieni wartość pliku cookie, chcemy, aby nasza aplikacja wiedziała o tym w NSHTTPCookieStorage
  • Jeśli serwer zmieni wartość pliku cookie, nie chcemy, aby nasze skrypty resetowały go z powrotem do pierwotnej wartości, gdy podążasz za linkiem / AJAX itp.

Więc zmodyfikowaliśmy nasz kod tak;

Tworzenie wniosku

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...

Daje to pewność, że pierwsze żądanie ma ustawione prawidłowe pliki cookie, bez wysyłania żadnych plików cookie z pamięci współdzielonej, które są przeznaczone dla innych domen, i bez wysyłania żadnych bezpiecznych plików cookie do niezabezpieczonego żądania.

Obsługa dalszych wniosków

Musimy również upewnić się, że inne żądania mają ustawione pliki cookie. Odbywa się to za pomocą skryptu uruchamianego podczas ładowania dokumentu, który sprawdza, czy jest ustawiony plik cookie, a jeśli nie, ustaw go na wartość w 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];

Radzenie sobie ze zmianami plików cookie

Musimy również poradzić sobie z serwerem zmieniającym wartość pliku cookie. Oznacza to dodanie kolejnego skryptu, aby wywołać z powrotem z widoku sieci Web, który tworzymy, aby zaktualizować nasz plik 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"];

i wdrożenie metody delegata w celu aktualizacji wszelkich plików cookie, które uległy zmianie, upewniając się, że aktualizujemy tylko pliki cookie z bieżącej domeny!

- (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];
        }
    }
}

Wydaje się, że rozwiązuje to nasze problemy z plikami cookie bez konieczności radzenia sobie z każdym miejscem, w którym używamy WKWebView w inny sposób. Możemy teraz po prostu użyć tego kodu jako pomocnika do tworzenia naszych widoków internetowych i w przejrzysty sposób aktualizuje się NSHTTPCookieStoragedla nas.


EDYCJA: Okazuje się, że użyłem prywatnej kategorii na NSHTTPCookie - oto kod:

- (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;
}
deanWombourne
źródło
6
Zapakowałem Twój kod w podklasę WKWebView. Zapraszam do sprawdzenia github.com/haifengkao/YWebView
Hai Feng Kao
Co się stanie, jeśli Twoje pliki cookie zawierają = znaki w wartości? Czy to zadziała?
iOSAddicted
@iOSAddicted tak myślę . Jeśli twoja wartość a=bbyłaby, skończyłbyś z łańcuchem cookie name=a=b;domain=.example.com;path=/- uważam, że standardowy dzieli się na, ;a następnie dzieli na pierwszy = z pary klucz = wartość. Chciałbym to jednak przetestować :)
dean Wombourne
twoja odpowiedź bardzo mi pomogła, chciałbym jednak dodać coś do twojego postu, istnieje kilka zagrożeń podczas korzystania z metody aktualizacji, niektóre frameworki JS mogą tworzyć pliki cookie o tej samej nazwie, ale innej domenie, a jeśli spróbujesz je zaktualizować używając metod js, istnieje duże ryzyko aktualizacji pliku cookie o niewłaściwej wartości. Również dla nas ciąg pliku cookie js musiał zostać pozbawiony bezpiecznej flagi, ponieważ nasz serwer dokonuje nieprzyjemnych przekierowań między http i https, powodując, że bezpieczne pliki cookie nie są obecne na niektórych stronach w niektórych nieprzyjemnych przypadkach skrajnych.
RicardoDuarte
Właściwie myślę, że firma, z którą byłem, kiedy to pisałem, musiała dodać do niej ochronę domeny po jej uruchomieniu. Nigdy (afaik) nie natknęliśmy się na bezpieczny / niezabezpieczony problem - brzmi to jak koszmar!
dziekan Wombourne,
48

Pliki cookie należy ustawić w konfiguracji przed utworzeniem WKWebViewpliku. W przeciwnym razie, nawet z WKHTTPCookieStore„s setCookieobsługi zakończeniu, ciasteczka nie będą niezawodnie być synchronizowane z widokiem internetowej. Ten wraca do tej linii od docs naWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

To @NSCopyingtrochę głęboka kopia. Wdrożenie mnie przerasta, ale efekt końcowy jest taki, że jeśli nie ustawisz plików cookie przed zainicjowaniem widoku internetowego, nie możesz liczyć na to, że pliki cookie tam są. Może to skomplikować architekturę aplikacji, ponieważ inicjowanie widoku staje się procesem asynchronicznym. Skończysz z czymś takim

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

a potem użyć czegoś takiego

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

Powyższy przykład odracza tworzenie widoku do ostatniej możliwej chwili, innym rozwiązaniem byłoby utworzenie konfiguracji lub widoku internetowego z dużym wyprzedzeniem i obsługa asynchronicznego charakteru przed utworzeniem kontrolera widoku.

Ostatnia uwaga: po utworzeniu tego widoku internetowego, wypuścisz go na wolność, nie możesz dodać więcej plików cookie bez użycia metod opisanych w tej odpowiedzi . Możesz jednak użyć WKHTTPCookieStoreObserverinterfejsu API, aby przynajmniej obserwować zmiany zachodzące w plikach cookie. Jeśli więc plik cookie sesji zostanie zaktualizowany w widoku internetowym, możesz ręcznie zaktualizować system za HTTPCookieStoragepomocą tego nowego pliku cookie, jeśli chcesz.

Aby uzyskać więcej informacji, przejdź do godziny 18:00 podczas ładowania niestandardowych treści internetowych sesji WWDC 2017 . Na początku tej sesji znajduje się zwodniczy przykład kodu, który pomija fakt, że widok sieciowy powinien zostać utworzony w module obsługi zakończenia.

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

Demo na żywo o 18:00 wyjaśnia to.

Edycja Od przynajmniej Mojave Beta 7 i iOS 12 Beta 7 widzę znacznie bardziej spójne zachowanie w przypadku plików cookie. setCookie(_:)Metoda wydaje się nawet, aby umożliwić ustawienie ciasteczka po WKWebViewzostał utworzony. Zrobiłem go znaleźć ważne jednak, aby nie dotknąć do processPoolzmiennej w ogóle. Funkcja ustawiania plików cookie działa najlepiej, gdy nie są tworzone żadne dodatkowe pule i gdy ta właściwość jest pozostawiona w spokoju. Myślę, że można bezpiecznie powiedzieć, że mieliśmy problemy z powodu błędów w WebKit.

nteissler
źródło
Wygląda na to, że obsługa / ustawienie plików cookie jest bardziej niezawodne w Mojave 10.14 beta 3 i iOS 12 beta 3
nteissler
8
Bardzo dogłębna i niedoceniana odpowiedź
Nicolás Carrasco
1
Nadal mam ten problem w iOS 12 z już załadowanym WKWebView. Czasami setCookie () będzie faktycznie zsynchronizowany z WKWebView od razu, czasami nie spowoduje to, że obsługa będzie nieco sporadyczna
bmjohns
Nadal widziałem problemy, odkąd radar został naprawiony, ale znacznie rzadziej. Jak często widzisz błąd dotyczący plików cookie? Jeśli masz odtwarzalny, wystarczająco mały projekt, naprawdę polecam zgłoszenie błędu zestawu webkit tutaj: webkit.org/reporting-bugs. Możesz także napisać na Twitterze do Brady Eidson (ładnie), architekt webkit w Apple, który bardzo szybko reaguje na tego rodzaju raporty i błędy.
nteissler
to jest prawidłowa odpowiedź - nie ma potrzeby ręcznego tłumaczenia plików cookie jako pól nagłówka w każdym żądaniu URLRequest, wystarczy użyć metody setCookie () zgodnie z opisem tutaj.
Guillaume Laurent
25

Pracuj dla mnie

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)
    }
}
user3589213
źródło
Niesamowity hack, zwłaszcza jak nieustępliwy iOS polega na zastępowaniu pliku cookie w istniejącym WKWebView. Jedynym problemem jest to, że poprzedni klucz WKNavigationKey stał się nieaktualny. Inny kod może na próżno czekać na starym.
BaseZen
2
czy to jest poprawne? Doceń, że może to zadziałać w pewnych okolicznościach. Jednak odpowiedzialność tej metody delegata - choosePolicyForNavigationAction - polega na decydowaniu o polityce; nie ładować żądania. To zostało zainicjowane wcześniej. W takim przypadku czy nie powoduje to dwukrotnego załadowania żądania?
Max MacLeod
2
@MaxMacLeod W elsewarunku, z którym wywołuje decisionHandlerzamknięcie, .cancelwięc w webviewrzeczywistości nie ładuje początkowego żądania. Po loadRequestwywołaniu elsewarunku ta metoda delegata zostanie ponownie wywołana dla tego żądania i przejdzie do ifwarunku, ponieważ Cookienagłówek będzie obecny.
halil_g
2
Chociaż to nie zadziała, gdy początkowe żądanie ma już ustawione pliki cookie, ponieważ nigdy nie przejdzie do elsestanu.
halil_g
Zwróć uwagę, że jest to 1) Nie działa w każdej sytuacji - na przykład w przypadku, gdy widok sieciowy ładuje ramki 2) Nie jest bezpieczny - może wysłać plik cookie z poufnymi informacjami na zewnętrzny adres URL
Peter Prokop
20

Oto moja wersja rozwiązania Mattrs w Swift do wstrzykiwania wszystkich plików cookie z HTTPCookieStorage. Zrobiono to głównie w celu wstrzyknięcia pliku cookie uwierzytelniania w celu utworzenia sesji użytkownika.

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
}
Misha
źródło
lepiej dodać tę linię, aby upewnić się, że formatowanie regionalne jest poprawne:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu
gdzie to nazwać?
markhorrocks
To zadziałało dobrze dla mnie w Swift 4 (z drobnymi poprawkami)
Frédéric Adda
U mnie to działa świetnie, ale dopiero przy drugiej wizycie w serwisie (pierwszy raz ciasteczka nie są ustawione) - ktoś się z tym spotkał?
MrChrisBarker
Pierwsze ładowanie daje błąd drugie ładowanie działa :( co może być problemem?
Shauket Sheikh
10

ustaw plik cookie

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

usuń plik cookie

self.webView.evaluateJavaScript("document.cookie='access_token=';domain='your domain';") { (data, error) -> Void in
        self.webView.reload()
}
cycDroid
źródło
9

Aktualizacja 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)
    }
}
Deep Parekh
źródło
1
Cześć, czy możesz również dodać kod, z którego będzie można pobierać pliki cookie HTTPCookieStorage.shared?
markhorrocks
Tylko w ten sposób otrzymałem WKWebView, aby dodać pliki cookie do każdego żądania
wysłanego
Jeśli w odpowiedzi zawiera plik cookie tylko http, nie można uzyskać w ten sposób wartości pliku cookie.
mózg
1
to tylko robi ustawianie plików cookie z powrotem do httpcookies przechowywania gdzie jest kod, który ustawia pliki cookie dla wkwebview?
Shauket Sheikh
8

Po przejrzeniu różnych odpowiedzi tutaj i bez powodzenia, przejrzałem dokumentację WebKit i natknąłem się na requestHeaderFieldsmetodę statyczną on HTTPCookie, która konwertuje tablicę plików cookie na format odpowiedni dla pola nagłówka. Połączenie tego z wglądem Mattr w aktualizację URLRequestpliku przed załadowaniem go z nagłówkami plików cookie pozwoliło mi dotrzeć do mety.

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)

Aby było to jeszcze prostsze, użyj rozszerzenia:

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

Teraz staje się po prostu:

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

To rozszerzenie jest również dostępne w LionheartExtensions, jeśli potrzebujesz tylko rozwiązania typu drop-in. Twoje zdrowie!

Dan Loewenherz
źródło
1
@ShauketSheikh hmm, w jakich sytuacjach to nie działa?
Dan Loewenherz
Testowałem używając symulatora ios 8, wygląda na to, że nie wysyła plików cookie. dwukrotnie to sprawdziłem.
Shauket Sheikh
Opublikowałem moją odpowiedź, możesz spróbować @Dan
Shauket Sheikh
7

W iOS 11 możesz teraz zarządzać ciasteczkami :), zobacz tę sesję: https://developer.apple.com/videos/play/wwdc2017/220/

wprowadź opis obrazu tutaj

LiangWang
źródło
2
@ShobhakarTiwari dlaczego? czy nastąpiły jakieś zmiany w oficjalnym wydaniu iOS11?
LiangWang,
Najlepszy sposób, jeśli obsługujesz tylko iOS 11 i nowsze wersje, jeśli potrzebujesz obsługiwać poprzednie wersje, użyj JavaScript przed załadowaniem strony.
PashaN
To działa dla mnie, z wyjątkiem faktu, że czasami metoda setcookie NIE uruchamia swojego modułu obsługi zakończenia, co oznacza, że ​​czasami moja strona internetowa nie ładuje się - dzieje się tylko na urządzeniu, zdarza się, że 3/4/5 raz zamykanie i ponowne otwieranie przeglądarka internetowa, a po tym, jak to się raz zdarzy, dzieje się tak, dopóki nie zresetuję aplikacji - czy ktoś też na to wpadnie?
Binya Koatz
5

Powodem opublikowania tej odpowiedzi jest to, że próbowałem wielu rozwiązań, ale nikt nie działał poprawnie, większość odpowiedzi nie działa w przypadku, gdy trzeba ustawić plik cookie za pierwszym razem, a plik cookie wynikowy nie jest synchronizowany za pierwszym razem. iOS> = 11.0 <= iOS 11 do 8.0, działa również z synchronizacją plików cookie za pierwszym razem.

Dla iOS> = 11.0 - Swift 4.2

Pobierz pliki cookie http i ustaw w sklepie plików cookie wkwebview w ten sposób, ładowanie żądania w wkwebview jest bardzo trudne , należy wysłać żądanie załadowania, gdy pliki cookie mają zostać ustawione w całości, oto funkcja, którą napisałem.

Wywołaj funkcję z zamknięciem po zakończeniu wywołujesz ładowanie widoku internetowego. FYI, ta funkcja obsługuje tylko iOS> = 11.0

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

Oto implementacja funkcji 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()
}
}

Od iOS 8 do iOS 11

musisz skonfigurować kilka dodatkowych rzeczy, musisz ustawić dwa pliki cookie, jeden za pomocą WKUserScript i nie zapomnij dodać plików cookie również w żądaniu, w przeciwnym razie plik cookie nie zostanie zsynchronizowany za pierwszym razem i zobaczysz, że strona nie ładuje się poprawnie za pierwszym razem. to do cholery, że obsługują pliki cookie dla iOS 8.0

przed utworzeniem obiektu 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))


}

Skoncentruj się na tej funkcji 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
}

Oto kolejny krok wkuserscript, który nie synchronizuje plików cookie od razu, jest dużo do cholery, aby załadować stronę za pierwszym razem z plikiem cookie, jeden z nich to ponowne załadowanie widoku internetowego, jeśli zakończy się proces, ale nie polecam go używać, nie jest to dobre z punktu widzenia użytkownika , do diabła, ilekroć będziesz gotowy do załadowania żądań ustaw pliki cookie w nagłówku żądania, a także w ten sposób, nie zapomnij dodać sprawdzania wersji iOS. przed żądaniem załadowania wywołaj tę funkcję.

request?.addCookies()

napisałem rozszerzenie dla 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

        }
    }

  }
}

teraz jesteś gotowy do testowania iOS> 8

Shauket Sheikh
źródło
2

Znajdź rozwiązanie, które najprawdopodobniej będzie dla Ciebie odpowiednie po wyjęciu z pudełka. Zasadniczo to zmienione i uaktualnione Swift 4 @ user3589213 „s odpowiedź .

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)
    }
}
Vadim Bulavin
źródło
2

Moja wersja odpowiedzi Nteiss. Testowany iOS 11, 12, 13. Wygląda na to, nie trzeba używać DispatchGroupna iOS 13więcej.

Używam funkcji non-statyczne includeCustomCookiesna WKWebViewConfiguration, tak, że można aktualizować cookiesza każdym razem tworzyć nowe 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()
        }
    }
}

Następnie używam tego w ten sposób:

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)
    })
}
Denis Kutlubaev
źródło
1

Wypróbowałem wszystkie powyższe odpowiedzi, ale żadna z nich nie działa. Po tylu próbach w końcu znalazłem niezawodny sposób na ustawienie pliku cookie WKWebview.

Najpierw musisz utworzyć instancję WKProcessPool i ustawić ją na WKWebViewConfiguration, która ma być używana do inicjalizacji samego WkWebview:

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

Ustawienie WKProcessPool jest tutaj najważniejszym krokiem. WKWebview wykorzystuje izolację procesów - co oznacza, że ​​działa w innym procesie niż proces Twojej aplikacji. Może to czasami powodować konflikt i uniemożliwić prawidłową synchronizację pliku cookie z WKWebview.

Przyjrzyjmy się teraz definicji WKProcessPool

Pula procesów skojarzona z widokiem WWW jest określona przez jego konfigurację widoku WWW. Każdy widok sieci Web otrzymuje własny proces zawartości sieci Web, aż do osiągnięcia limitu procesu określonego w implementacji; następnie widoki WWW z tą samą pulą procesów współużytkują procesy treści WWW.

Zwróć uwagę na ostatnie zdanie, jeśli planujesz używać tego samego WKWebview do żądań podciągów

widoki WWW z tą samą pulą procesów kończą współużytkowanie procesów treści WWW

mam na myśli to, że jeśli nie używasz tego samego wystąpienia WKProcessPool za każdym razem, gdy konfigurujesz WKWebView dla tej samej domeny (być może masz VC A, który zawiera WKWebView i chcesz utworzyć różne wystąpienia VC A w różnych miejscach ), mogą wystąpić konflikty plików cookie. Aby rozwiązać problem, po pierwszym utworzeniu WKProcessPool dla WKWebView, który ładuje domenę B, zapisuję go w singletonie i używam tego samego WKProcessPool za każdym razem, gdy muszę utworzyć WKWebView, który ładuje tę samą domenę 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
}()

Po zakończeniu procesu inicjalizacji, można załadować URLRequest wewnątrz bloku zakończenia dnia httpCookieStore.setCookie. Tutaj musisz dołączyć plik cookie do nagłówka żądania, w przeciwnym razie nie zadziała.

P / s: Ukradłem rozszerzenie z fantastycznej odpowiedzi powyżej Dana Loewenherza

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)
   }
}
Linh Ta
źródło
0

Lepszym rozwiązaniem dla wniosków XHR pokazany jest tutaj

Wersja 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)
}
Lloyd Keijzer
źródło
0

Jeśli ktoś używa Alamofire, to jest to lepsze rozwiązanie.

  let cookies = Alamofire.SessionManager.default.session.configuration.httpCookieStorage?.cookies(for: URL(string: BASE_URL)!)
  for (cookie) in cookies ?? [] {
      webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
  }
jeet.chanchawat
źródło
0

To działa dla mnie: po setcookies dodaj 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))
                        }

                    })
                }
adar tzeiri
źródło
0

Dodając wielokrotne pliki cookie, możesz to zrobić w następujący sposób: ( path& domainjest wymagane dla każdego elementu)

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];

w przeciwnym razie zostanie ustawiony tylko pierwszy element cookie.

YanXing Ou
źródło
0

Możesz również użyć WKWebsiteDataStore, aby uzyskać podobne zachowanie do HTTPCookieStorage z UIWebView.

let dataStore = WKWebsiteDataStore.default()
let cookies = HTTPCookieStorage.shared.cookies ?? [HTTPCookie]()
cookies.forEach({
    dataStore.httpCookieStore.setCookie($0, completionHandler: nil)
})
Tigu
źródło
0

Poniższy kod działa dobrze w moim projekcie Swift5. spróbuj załadować adres URL przez WKWebView poniżej:

    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)
    }
Fasola Vansa
źródło
0

To jest moje rozwiązanie do obsługi plików cookie i WKWebView w systemie iOS 9 lub nowszym.

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

}
Giuseppe Mazzilli
źródło
0

Ten błąd, który zrobiłem, polegał na tym, że przekazałem cały adres URL w atrybucie domeny, powinien to być tylko nazwa domeny.

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

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
Muhammad Aamir Ali
źródło
0

Rozwiązanie dla iOS 10+

Detale

  • Swift 5.1
  • Xcode 11.6 (11E708)

Rozwiązanie

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

Stosowanie

Nie zapomnij wkleić tutaj kodu rozwiązania

class WebViewController: UIViewController {
   
    private let host = "google.com"
    private weak var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupWebView()
    }
    
    func setupWebView() {
        let cookies: [HTTPCookie] = []
        let configuration = WKWebViewConfiguration()
        configuration.websiteDataStore = .nonPersistent()
        configuration.set(cookies: cookies) {
            let webView = WKWebView(frame: .zero, configuration: configuration)
            /// ..
            self.webView = webView
            
            self.loadPage(url: URL(string:self.host)!)
        }
    }
    
    private func loadPage(url: URL) {
        var request = URLRequest(url: url)
        request.setCookies()
        webView.load(request)
    }
}

extension WebViewController: WKNavigationDelegate {

     // https://stackoverflow.com/a/47529039/4488252
     func webView(_ webView: WKWebView,
                  decidePolicyFor navigationAction: WKNavigationAction,
                  decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

         if #available(iOS 11.0, *) {
             decisionHandler(.allow)
         } else {
             guard let url = navigationAction.request.url, let host = url.host, host.contains(self.host) else {
                 decisionHandler(.allow)
                 return
             }

             if navigationAction.request.hasCookies {
                 decisionHandler(.allow)
             } else {
                 DispatchQueue.main.async {
                     decisionHandler(.cancel)
                     self.loadPage(url: url)
                 }
             }
         }
     }
 }

Pełna próbka

Nie zapomnij wkleić tutaj kodu rozwiązania

import UIKit
import WebKit

class ViewController: UIViewController {

    private weak var webView: WKWebView!
    let url = URL(string: "your_url")!
    
    var cookiesData: [String : Any]  {
        [
            "access_token": "your_token"
        ]
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let configuration = WKWebViewConfiguration()
        
        guard let host = self.url.host else { return }
        configuration.set(cookies: createCookies(host: host, parameters: self.cookiesData)) {
            let webView = WKWebView(frame: .zero, configuration: configuration)
            self.view.addSubview(webView)
            self.webView = webView
            webView.navigationDelegate = self
            webView.translatesAutoresizingMaskIntoConstraints = false
            webView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
            webView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
            self.view.bottomAnchor.constraint(equalTo: webView.bottomAnchor).isActive = true
            self.view.rightAnchor.constraint(equalTo: webView.rightAnchor).isActive = true

            self.loadPage(url: self.url)
        }
    }

    private func loadPage(url: URL) {
        var request = URLRequest(url: url)
        request.timeoutInterval = 30
        request.setCookies()
        webView.load(request)
    }
    
    private func createCookies(host: String, parameters: [String: Any]) -> [HTTPCookie] {
        parameters.compactMap { (name, value) in
            HTTPCookie(properties: [
                .domain: host,
                .path: "/",
                .name: name,
                .value: "\(value)",
                .secure: "TRUE",
                .expires: Date(timeIntervalSinceNow: 31556952),
            ])
        }
    }
}

extension ViewController: WKNavigationDelegate {

    // https://stackoverflow.com/a/47529039/4488252
    func webView(_ webView: WKWebView,
                 decidePolicyFor navigationAction: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

        if #available(iOS 11.0, *) {
            decisionHandler(.allow)
        } else {
            guard let url = navigationAction.request.url, let host = url.host, host.contains(self.url.host!) else {
                decisionHandler(.allow)
                return
            }

            if navigationAction.request.hasCookies {
                decisionHandler(.allow)
            } else {
                DispatchQueue.main.async {
                    decisionHandler(.cancel)
                    self.loadPage(url: url)
                }
            }
        }
    }
}

Info.plist

dodaj swoje ustawienia bezpieczeństwa transportu Info.plist

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
Wasilij Bodnarczuk
źródło
0

Oto jak to robię-

wywołaj initWebConfig w didFinishLaunchingWithOptions z AppDelegate (lub gdziekolwiek przed utworzeniem WebView), w przeciwnym razie czasami pliki cookie nie synchronizują się prawidłowo -

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

W razie potrzeby ustaw pliki cookie w grupie wysyłkowej

 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)
                }
Vishwas Singh
źródło