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 =
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/index.html"]];
[request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"];
[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];
[userContentController addUserScript:cookieScript];
WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
webViewConfig.userContentController = userContentController;
WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake() 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!
Po zabawie z tą odpowiedzią (która była fantastycznie pomocna :) musieliśmy wprowadzić kilka zmian:
NSHTTPCookieStorage
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ę
NSHTTPCookieStorage
dla 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; }
źródło
a=b
byłaby, skończyłbyś z łańcuchem cookiename=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ć :)Pliki cookie należy ustawić w konfiguracji przed utworzeniem
WKWebView
pliku. W przeciwnym razie, nawet zWKHTTPCookieStore
„ssetCookie
obsługi zakończeniu, ciasteczka nie będą niezawodnie być synchronizowane z widokiem internetowej. Ten wraca do tej linii od docs naWKWebViewConfiguration
To
@NSCopying
trochę 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ś takimextension 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ć
WKHTTPCookieStoreObserver
interfejsu 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 zaHTTPCookieStorage
pomocą 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.
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 poWKWebView
został utworzony. Zrobiłem go znaleźć ważne jednak, aby nie dotknąć doprocessPool
zmiennej 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.źródło
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) } }
źródło
else
warunku, z którym wywołujedecisionHandler
zamknięcie,.cancel
więc wwebview
rzeczywistości nie ładuje początkowego żądania. PoloadRequest
wywołaniuelse
warunku ta metoda delegata zostanie ponownie wywołana dla tego żądania i przejdzie doif
warunku, ponieważCookie
nagłówek będzie obecny.else
stanu.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 }
źródło
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
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() }
źródło
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) } }
źródło
HTTPCookieStorage.shared
?Po przejrzeniu różnych odpowiedzi tutaj i bez powodzenia, przejrzałem dokumentację WebKit i natknąłem się na
requestHeaderFields
metodę statyczną onHTTPCookie
, która konwertuje tablicę plików cookie na format odpowiedni dla pola nagłówka. Połączenie tego z wglądem Mattr w aktualizacjęURLRequest
pliku 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!
źródło
W iOS 11 możesz teraz zarządzać ciasteczkami :), zobacz tę sesję: https://developer.apple.com/videos/play/wwdc2017/220/
źródło
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
źródło
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) } }
źródło
Moja wersja odpowiedzi Nteiss. Testowany
iOS 11, 12, 13
. Wygląda na to, nie trzeba używaćDispatchGroup
naiOS 13
więcej.Używam funkcji non-statyczne
includeCustomCookies
naWKWebViewConfiguration
, tak, że można aktualizowaćcookies
za każdym razem tworzyć noweWKWebViewConfiguration
.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) }) }
źródło
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
Zwróć uwagę na ostatnie zdanie, jeśli planujesz używać tego samego WKWebview do żądań podciągów
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) } }
źródło
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) }
źródło
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) }
źródło
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)) } }) }
źródło
Dodając wielokrotne pliki cookie, możesz to zrobić w następujący sposób: (
path
&domain
jest 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.
źródło
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) })
źródło
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) }
źródło
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!) } }
źródło
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)
źródło
Rozwiązanie dla iOS 10+
Detale
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
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
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
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
źródło
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) }
źródło