¿Puedo configurar las cookies para que las use WKWebView?

135

Estoy tratando de cambiar una aplicación existente de UIWebViewa WKWebView. La aplicación actual administra el inicio de sesión / sesión de los usuarios fuera del webviewy establece los cookiesrequisitos para la autenticación en el NSHTTPCookieStore. Lamentablemente nuevo WKWebViewno utiliza el cookiesde NSHTTPCookieStorage. ¿Hay otra forma de lograr esto?

Columna
fuente

Respuestas:

186

Editar solo para iOS 11+

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

Como los está retirando de HTTPCookeStorage, puede hacer esto:

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

Respuesta anterior para iOS 10 y abajo

Si necesita que sus cookies se configuren en la solicitud de carga inicial, puede configurarlas en NSMutableURLRequest. Debido a que las cookies son solo un encabezado de solicitud con formato especial, esto se puede lograr así:

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

Si necesita posteriores solicitudes de AJAX en la página para configurar sus cookies, esto se puede lograr simplemente usando WKUserScript para establecer los valores mediante programación a través de JavaScript al inicio del documento de la siguiente manera:

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

La combinación de estas dos técnicas debería proporcionarle herramientas suficientes para transferir valores de cookies de Native App Land a Web View Land. Puede encontrar más información sobre la API de JavaScript de cookies en la página de Mozilla si necesita algunas cookies más avanzadas.

Sí, apesta que Apple no sea compatible con muchas de las sutilezas de UIWebView . No estoy seguro de si alguna vez los apoyarán, pero espero que lo logren pronto. ¡Espero que esto ayude!

mattr
fuente
1
¿Dónde está el mejor lugar para inyectar cookies para solicitudes posteriores? Por ejemplo, la carga de la página inicial se trata en la respuesta anterior, pero ¿qué pasa si hay enlaces en la página que también conducen al mismo dominio y también necesitan las mismas cookies inyectadas en la solicitud? didStartProvisionalNavigation?
Mason G. Zhwiti
1
lo siento, no te funciona. En mi opinión, siempre que los dominios sean los mismos, no debería haber ningún problema. ¿Puede volver a verificar el código que el enlace apunta al mismo dominio desde el que cargó la solicitud? Además, las cookies también pueden limitarse a una "ruta" específica. Tal vez eso está causando algunos problemas?
Mattr
11
Tenga en cuenta que la técnica de JavaScript para configurar las cookies no funcionará para las cookies "HTTP Only".
Ahmed Nasser
1
El método anterior funciona muy bien ... pero pude ver cookies duplicadas en las siguientes llamadas AJAX (duplicadas solo una vez).
Durga Vundavalli
1
@ Axel92Dev una solución alternativa sería asegurarse de que la primera solicitud realizada desde su vista web a su servidor reciba una respuesta que explícitamente le indique a la vista web que configure las cookies nuevamente con el indicador HTTPOnly (es decir: configure las cookies nuevamente en la respuesta). Puede crear una API especial para ese único propósito al inicializar la vista web, luego usar la vista web normalmente en caso de éxito.
Ahmed Nasser
64

Después de jugar con esta respuesta (que fue increíblemente útil :) hemos tenido que hacer algunos cambios:

  • Necesitamos vistas web para manejar múltiples dominios sin filtrar información de cookies privadas entre esos dominios.
  • Lo necesitamos para honrar las cookies seguras
  • Si el servidor cambia un valor de cookie, queremos que nuestra aplicación lo sepa en NSHTTPCookieStorage
  • Si el servidor cambia un valor de cookie, no queremos que nuestros scripts lo restablezcan a su valor original cuando sigue un enlace / AJAX, etc.

Así que modificamos nuestro código para que sea esto;

Crear una solicitud

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

Esto asegura que la primera solicitud tenga las cookies correctas establecidas, sin enviar ninguna cookie del almacenamiento compartido que sea para otros dominios, y sin enviar cookies seguras a una solicitud insegura.

Manejo de nuevas solicitudes

También debemos asegurarnos de que otras solicitudes tengan configuradas las cookies. Esto se realiza mediante un script que se ejecuta en la carga de documentos que verifica si hay un conjunto de cookies y, si no, configúrelo en el valor 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];

Manejo de cambios de cookies

También tenemos que lidiar con el servidor cambiando el valor de una cookie. Esto significa agregar otro script para volver a llamar desde la vista web que estamos creando para actualizar nuestro 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"];

e implementando el método delegado para actualizar las cookies que han cambiado, ¡asegurándonos de que solo estamos actualizando las cookies del dominio actual!

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

Esto parece solucionar nuestros problemas de cookies sin que tengamos que lidiar con cada lugar que usamos WKWebView de manera diferente. Ahora podemos usar este código como ayudante para crear nuestras vistas web y se actualiza de forma transparente NSHTTPCookieStoragepara nosotros.


EDITAR: Resulta que utilicé una categoría privada en NSHTTPCookie; aquí está el código:

- (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
fuente
66
He envuelto su código en una subclase de WKWebView. Siéntase libre de visitarlo github.com/haifengkao/YWebView
Hai Feng Kao
¿Qué pasa si sus cookies contienen = signos en el valor? ¿Funcionaría esto?
iOSAdictado
@iOSAddicted Creo que sí. Si su valor fuera a=b, terminaría con la cadena de cookies name=a=b;domain=.example.com;path=/: creo que el estándar se divide ;y luego se divide en el primero = en el par clave = valor. Aunque probaría esto :)
deanWombourne
su respuesta me ayudó mucho, sin embargo, me gustaría agregar algo a su publicación, existen varios riesgos al usar su método de actualización, algunos marcos JS pueden crear cookies que tienen el mismo nombre pero un dominio diferente, y si intenta actualizarlo Al utilizar los métodos js, tiene un alto riesgo de actualizar una cookie con un valor incorrecto. También para nosotros, la cadena de cookies js, tuvo que ser despojada de su bandera segura, ya que nuestro servidor realiza redireccionamientos desagradables entre http y https, lo que hace que las cookies seguras no estén presentes en algunas páginas en algunos casos extremos desagradables.
RicardoDuarte
De hecho, creo que la compañía con la que estaba cuando escribí esto tuvo que agregarle protección de dominio después de que se lanzó. Nunca (afaik) nos encontramos con el problema seguro / inseguro, ¡suena como una pesadilla!
deanWombourne
42

Las cookies deben establecerse en la configuración antes de que WKWebViewse cree. De lo contrario, incluso con WKHTTPCookieStoreel setCookiecontrolador de finalización, las cookies no se sincronizarán de manera confiable con la vista web. Esto vuelve a esta línea desde los documentos enWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

Eso @NSCopyinges una especie de copia profunda. La implementación está más allá de mí, pero el resultado final es que, a menos que establezca cookies antes de inicializar la vista web, no puede contar con las cookies. Esto puede complicar la arquitectura de la aplicación porque la inicialización de una vista se convierte en un proceso asincrónico. Terminarás con algo como esto

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

y luego usarlo como

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

El ejemplo anterior difiere la creación de vistas hasta el último momento posible, otra solución sería crear la configuración o vista web con mucha anticipación y manejar la naturaleza asincrónica antes de la creación de un controlador de vista.

Una nota final: una vez que crea esta vista web, la ha dejado libre, no puede agregar más cookies sin usar los métodos descritos en esta respuesta . Sin embargo, puede usar la WKHTTPCookieStoreObserverAPI para al menos observar los cambios que ocurren en las cookies. Entonces, si una cookie de sesión se actualiza en la vista web, puede actualizar manualmente el sistema HTTPCookieStoragecon esta nueva cookie si lo desea.

Para más información sobre esto, pase a las 18:00 en este carga de contenido web personalizado de la sesión WWDC 2017 . Al comienzo de esta sesión, hay un ejemplo de código engañoso que omite el hecho de que la vista web debe crearse en el controlador de finalización.

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

La demostración en vivo a las 18:00 aclara esto.

Editar A partir de Mojave Beta 7 y iOS 12 Beta 7, al menos, veo un comportamiento mucho más consistente con las cookies. El setCookie(_:)método incluso parece permitir la configuración de cookies después de que WKWebViewse haya creado. Sin embargo, me pareció importante no tocar la processPoolvariable en absoluto. La funcionalidad de configuración de cookies funciona mejor cuando no se crean grupos adicionales y cuando esa propiedad se deja sola. Creo que es seguro decir que tuvimos problemas debido a algunos errores en WebKit.

nteissler
fuente
Parece que el manejo / configuración de cookies es más confiable en Mojave 10.14 beta 3 y iOS 12 beta 3
nteissler
66
Respuesta muy profunda y subestimada
Nicolás Carrasco
1
Todavía tengo este problema en iOS 12 con un WKWebView ya cargado. A veces setCookie () en realidad se sincronizará con el WKWebView de inmediato, a veces no va a hacer que el manejo de algo esporádica
bmjohns
Todavía he visto problemas también desde que el radar se afirmó arreglado, pero con mucha menos frecuencia. ¿Con qué frecuencia está viendo la falla de la cookie? Si tiene un proyecto reproducible y lo suficientemente pequeño, realmente recomendaría enviar un error de webkit aquí: webkit.org/reporting-bugs También puede twittear a Brady Eidson (muy bien) un arquitecto de webkit en apple que responde muy bien a este tipo de Informes y errores.
nteissler
esta es la respuesta correcta: no es necesario traducir manualmente las cookies como campos de encabezado en cada URLRequest, es solo que setCookie () debe usarse como se describe aquí.
Guillaume Laurent
25

trabaja para mi

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)
    }
}
usuario3589213
fuente
Hack impresionante, especialmente cuán intransigente es iOS sobre anular una cookie en un WKWebView existente. El único problema es que la WKNavigationKey anterior se ha vuelto obsoleta. Otro código puede estar esperando en vano en el antiguo.
BaseZen
2
¿es esto correcto? Aprecio que puede funcionar en algunas circunstancias. Sin embargo, la responsabilidad de este método de delegado (decidePolicyForNavigationAction) es decidir la política; no cargar realmente la solicitud. Eso ha sido iniciado previamente. En ese caso, ¿esto no hace que la solicitud se cargue dos veces?
Max MacLeod
2
@MaxMacLeod En la elsecondición con la que está llamando al decisionHandlercierre, por .cancello webviewque en realidad no carga la solicitud inicial. Después de que loadRequestse llame a la elsecondición, este método de delegado se volverá a llamar para esa solicitud y entrará en la ifcondición porque el Cookieencabezado estará presente.
halil_g
2
Aunque esto no funcionaría cuando la solicitud inicial ya tenga algunas cookies configuradas, ya que nunca entrará en la elsecondición.
halil_g
Tenga en cuenta que esto es 1) No funciona para todas las situaciones, por ejemplo, en el caso de que la vista web cargue marcos 2) No es seguro: podría enviar cookies con información confidencial a URL de terceros
Peter Prokop
20

Aquí está mi versión de la solución Mattrs en Swift para inyectar todas las cookies de HTTPCookieStorage. Esto se hizo principalmente para inyectar una cookie de autenticación para crear una sesión de usuario.

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
fuente
mejor agregar esta línea para asegurarse de que el formato de la configuración regional es correcto:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu
donde llamar a esto?
markhorrocks
Esto funcionó bien para mí en Swift 4 (con ajustes menores)
Frédéric Adda
Esto funciona muy bien para mí, pero solo la segunda vez que visito el sitio (la primera vez que no se configuran las cookies), ¿alguien se encuentra con esto?
MrChrisBarker
Primera carga da error trabajo de segunda carga :( ¿cuál podría ser el problema?
Shauket Sheikh
10

establecer cookie

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

eliminar cookie

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

Actualización de 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)
    }
}
Parekh profundo
fuente
1
Hola, ¿también puedes agregar código para obtener cookies HTTPCookieStorage.shared?
markhorrocks
Esta es la única forma en que obtuve WKWebView para agregar cookies a cada solicitud realizada por la vista web
Chicowitz
Si contiene la cookie httponly en respuesta, no podría obtener el valor de las cookies de esta manera.
cerebro
1
esto solo hace que las cookies vuelvan al almacenamiento de httpcookies, ¿dónde está el código que configura las cookies para wkwebview?
Shauket Sheikh
8

Después de buscar varias respuestas aquí y no tener éxito, revisé la documentación de WebKit y me topé con el requestHeaderFieldsmétodo estático HTTPCookie, que convierte una matriz de cookies en un formato adecuado para un campo de encabezado. La combinación de esto con la idea de Mattr de actualizar URLRequestantes de cargarlo con los encabezados de las cookies me ayudó a llegar a la meta.

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)

Para hacer esto aún más simple, use una extensión:

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

Ahora solo se convierte en:

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

Esta extensión también está disponible en LionheartExtensions si solo desea una solución directa. ¡Salud!

Dan Loewenherz
fuente
1
@ShauketSheikh hmm, ¿en qué situaciones esto no funciona?
Dan Loewenherz
Lo probé usando el simulador ios 8, parece que no envía cookies. Lo he comprobado dos veces.
Shauket Sheikh
publiqué mi respuesta, puedes probar @Dan
Shauket Sheikh el
7

En iOS 11, puede administrar las cookies ahora :), vea esta sesión: https://developer.apple.com/videos/play/wwdc2017/220/

ingrese la descripción de la imagen aquí

Jacky
fuente
2
@ShobhakarTiwari ¿por qué? ¿Se producen cambios en el lanzamiento formal de iOS11?
Jacky
La mejor manera de hacerlo si solo es compatible con iOS 11 y versiones posteriores, si necesita admitir versiones anteriores, use JavaScript antes de cargar la página.
PashaN
Esto funciona para mí, excepto por el hecho de que a veces el método setcookie NO ejecuta su controlador de finalización, lo que significa que a veces mi página web no se carga, solo ocurre en el dispositivo, ocurre la tercera / cuarta / quinta vez al cerrar y reabrir la vista web, y después de que sucede una vez, sigue sucediendo hasta que reinicio la aplicación, ¿alguien también se encuentra con esto?
Binya Koatz
5

La razón detrás de esta respuesta publicada es que probé muchas soluciones, pero ninguna funcionó correctamente, la mayoría de las respuestas no funcionan en caso de que tenga que configurar la cookie la primera vez, y obtuve el resultado de que la cookie no se sincronice la primera vez, use esta solución, funciona para ambos iOS> = 11.0 <= iOS 11 hasta 8.0, también funciona con sincronización de cookies la primera vez.

Para iOS> = 11.0 - Swift 4.2

Obtenga cookies http y configúrelas en wkwebview la tienda de cookies esta manera, es un punto muy complicado cargar su solicitud en wkwebview , debe enviar una solicitud de carga cuando las cookies se configurarán por completo, esta es la función que escribí.

Llame a la función con el cierre finalizado. Llame cargar webview. FYI esta función solo maneja iOS> = 11.0

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

Aquí está la implementación de la función 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()
}
}

Para iOS 8 hasta iOS 11

necesita configurar algunas cosas adicionales que necesita para configurar dos cookies una vez usando WKUserScript y no olvide agregar las cookies también, de lo contrario, su cookie no se sincronizará la primera vez y verá que su página no se carga correctamente la primera vez. este es el diablo que encontré para admitir cookies para iOS 8.0

antes de Wkwebview creación de objetos.

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


}

Concéntrese en esta función 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
}

Aquí hay otro paso: wkuserscript no sincroniza las cookies de inmediato, hay que cargar mucho la primera página con la cookie, una es volver a cargar la vista web si finaliza el proceso, pero no recomiendo usarla, no es bueno para el punto de vista del usuario , diablos, siempre que esté listo para cargar las cookies de conjunto de solicitudes en el encabezado de la solicitud y de esta manera, no olvide agregar la verificación de la versión de iOS. antes de la solicitud de carga, llame a esta función.

request?.addCookies()

escribí extensión para 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

        }
    }

  }
}

ahora estás listo para probar iOS> 8

Shauket Sheikh
fuente
2

Encuentre la solución que probablemente funcione para usted de manera inmediata. Básicamente está modificado y actualizado para la respuesta de Swift 4 @ user3589213 .

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    let headerKeys = navigationAction.request.allHTTPHeaderFields?.keys
    let hasCookies = headerKeys?.contains("Cookie") ?? false

    if hasCookies {
        decisionHandler(.allow)
    } else {
        let cookies = HTTPCookie.requestHeaderFields(with: HTTPCookieStorage.shared.cookies ?? [])

        var headers = navigationAction.request.allHTTPHeaderFields ?? [:]
        headers += cookies

        var req = navigationAction.request
        req.allHTTPHeaderFields = headers

        webView.load(req)

        decisionHandler(.cancel)
    }
}
Vadim Bulavin
fuente
1

He intentado todas las respuestas anteriores pero ninguna de ellas funciona. Después de tantos intentos, finalmente encontré una forma confiable de configurar la cookie WKWebview.

Primero debe crear una instancia de WKProcessPool y configurarla en la WKWebViewConfiguration que se utilizará para inicializar la propia WkWebview:

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

Configurar WKProcessPool es el paso más importante aquí.WKWebview hace uso del aislamiento de procesos, lo que significa que se ejecuta en un proceso diferente al proceso de su aplicación. Esto a veces puede causar conflictos y evitar que su cookie se sincronice correctamente con WKWebview.

Ahora veamos la definición de WKProcessPool

El grupo de procesos asociado con una vista web se especifica mediante su configuración de vista web. Cada vista web tiene su propio proceso de Contenido web hasta que se alcanza un límite de proceso definido por la implementación; después de eso, las vistas web con el mismo grupo de procesos terminan compartiendo procesos de contenido web.

Preste atención a la última oración si planea usar la misma WKWebview para solicitudes de subsecuencia

las vistas web con el mismo grupo de procesos terminan compartiendo procesos de contenido web

lo que quiero decir es que si no usa la misma instancia de WKProcessPool cada vez que configura un WKWebView para el mismo dominio (tal vez tenga un VC A que contenga un WKWebView y desee crear diferentes instancias de VC A en diferentes lugares ), puede haber conflictos al configurar las cookies. Para resolver el problema, después de la primera creación de WKProcessPool para un WKWebView que carga el dominio B, lo guardo en un singleton y uso ese mismo WKProcessPool cada vez que tengo que crear un WKWebView que carga el mismo dominio 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
}()

Después del proceso de inicialización, puede cargar una URLRequest dentro del bloque de finalización de httpCookieStore.setCookie. Aquí, debe adjuntar la cookie al encabezado de la solicitud contrario, no funcionará.

P / s: robé la extensión de la fantástica respuesta anterior de Dan Loewenherz

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
fuente
1

Mi versión de la respuesta de nteiss. Probado en iOS 11, 12, 13. Parece que no tienen que utilizar DispatchGroupen iOS 13más.

Yo uso la función no estática includeCustomCookiessobre WKWebViewConfiguration, de modo que pueda actualizar cookiescada vez que crear nuevas 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()
        }
    }
}

Entonces lo uso así:

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
fuente
0

Aquí se muestra la mejor solución para las solicitudes XHR

Versión 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
fuente
0

Si alguien está usando Alamofire, entonces esta es una mejor solución.

  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
fuente
0

Esto funciona para mí: después de configurar las cookies, agregue 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
fuente
0

Al agregar múltiples elementos de cookies, puede hacerlo así: ( pathy domainse requiere para cada elemento)

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

de lo contrario, solo se establecerá el primer elemento de cookie.

YanXing Ou
fuente
0

También puede usar WKWebsiteDataStore para obtener un comportamiento similar a HTTPCookieStorage de UIWebView.

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

El siguiente código funciona bien en mi proyecto Swift5. intente cargar url por WKWebView a continuación:

    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)
    }
Frijol Vansa
fuente
0

Esta es mi solución para manejar con Cookies y WKWebView en iOS 9 o posterior.

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
fuente
0

El error que estaba cometiendo es que estaba pasando toda la URL en el atributo de dominio, debería ser solo el nombre de dominio.

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

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
Muhammad Aamir Ali
fuente