J'essaie de passer d'une application existante d'UIWebView à WKWebView. L'application actuelle gère les connexions/sessions des utilisateurs en dehors de l'affichage Web et définit les cookies nécessaires à l'authentification dans NSHTTPCookieStore. Malheureusement, le nouveau WKWebView n'utilise pas les cookies du NSHTTPCookieStorage. Y a-t-il un autre moyen d'y parvenir?
Après avoir joué avec cette réponse (ce qui était incroyablement utile :), nous avons dû apporter quelques modifications:
NSHTTPCookieStorage
Nous avons donc modifié notre code pour être ceci;
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...
Cela garantit que les cookies corrects sont configurés dans la première demande, sans envoyer de cookies du stockage partagé qui sont destinés à d'autres domaines et sans envoyer de cookies sécurisés dans une demande non sécurisée.
Nous devons également nous assurer que les cookies sont définis dans les autres demandes. Cette opération est effectuée à l'aide d'un script qui s'exécute lors du chargement de document, qui vérifie s'il existe un cookie et, dans le cas contraire, le définit sur la valeur 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];
Nous devons également traiter le serveur en modifiant la valeur d'un cookie. Cela signifie qu’il faut ajouter un autre script pour rappeler la vue Web que nous créons afin de mettre à jour notre 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"];
et en implémentant la méthode déléguée pour mettre à jour les cookies qui ont été modifiés, en veillant à ne mettre à jour que les cookies du domaine actuel!
- (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];
}
}
}
Cela semble résoudre nos problèmes de cookies sans que nous ayons à gérer chaque endroit où nous utilisons WKWebView différemment. Nous pouvons maintenant simplement utiliser ce code comme aide pour créer nos vues Web et il met à jour de manière transparente NSHTTPCookieStorage
pour nous.
EDIT: Il se trouve que j'ai utilisé une catégorie privée sur NSHTTPCookie - voici le code:
- (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;
}
travaille pour moi
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)
}
}
Voici ma version de Mattrs solution dans Swift pour l’injection de tous les cookies de HTTPCookieStorage. Cela a été fait principalement pour injecter un cookie d'authentification afin de créer une session d'utilisateur.
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
}
Swift 3 mise à jour:
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)
}
}
mettre cookie
self.webView.evaluateJavaScript("document.cookie='access_token=your token';domain='your domain';") { (data, error) -> Void in
self.webView.reload()
}
supprimer le cookie
self.webView.evaluateJavaScript("document.cookie='access_token=';domain='your domain';") { (data, error) -> Void in
self.webView.reload()
}
Dans iOS 11, vous pouvez gérer le cookie maintenant :), consultez la session suivante: https://developer.Apple.com/videos/play/wwdc2017/220/
Après avoir parcouru diverses réponses ici sans succès, j'ai feuilleté la documentation WebKit et suis tombé sur la méthode statique requestHeaderFields
sur HTTPCookie
, qui convertit un tableau de cookies en un format adapté à un champ d'en-tête. La combinaison de ceci avec l'intelligence de mattr de la mise à jour de la URLRequest
avant de la charger avec les en-têtes de cookie m'a permis de franchir la ligne d'arrivée.
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)
Pour rendre cela encore plus simple, utilisez une extension:
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)
}
}
Maintenant ça devient:
let request = URLRequest(url: URL(string: "https://example.com/")!)
let webView = WKWebView(frame: self.view.frame)
webView.load(request, with: cookies)
Cette extension est également disponible dans LionheartExtensions si vous souhaitez simplement une solution d’arrivée. À votre santé!
Veuillez trouver la solution la plus susceptible de fonctionner pour vous dès la sortie de la boîte. En gros, il est modifié et mis à jour pour Swift 4 @ user3589213 _ answer .
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)
}
}
La raison derrière cette réponse est que j'ai essayé plusieurs solutions mais que je ne fonctionne pas correctement, la plupart des réponses ne fonctionnent pas dans lesquelles les cookies ne sont pas synchronisés pour la première fois. Veuillez utiliser cette solution pour les deux versions iOS supérieures à 11.0 et inférieures à iOS 11 à 8.0. travailler avec la synchronisation des cookies pour la première fois.
Pour iOS> = 11.0 -- Swift 4.2
Obtenez http cookies et définissez-le dans wkwebview magasin de cookies comme ceci: il est très délicat de charger votre demande dans wkwebview, vous devez envoyer la demande de chargement lorsque les cookies sont complètement configurés. fonction que j'ai écrit.
En appelant la fonction avec fermeture, vous appelez load webview. Pour info, cette fonction ne gère que iOS> = 11.0
self.WwebView.syncCookies {
if let request = self.request {
self.WwebView.load(request)
}
}
Voici l'implémentation de la fonction 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()
}
}
Pour iOS 8 à iOS 11
vous devez configurer quelques éléments supplémentaires dont vous avez besoin pour configurer deux témoins de temps, l'un à l'aide de WKUserScript, et n'oubliez pas d'ajouter des cookies également, sinon votre cookie ne se synchronisera pas la première fois et votre page ne se chargera pas la première fois. correctement. c'est le diable que j'ai trouvé pour supporter les cookies pour iOS 8.0
avant de créer un objet 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))
}
Focus sur cette fonction 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
}
Voici une autre étape, wkuserscript ne synchronise pas les cookies immédiatement, il y a beaucoup de choses à faire pour charger la première page avec un cookie: recharger à nouveau la vue Web s'il termine le processus, mais je ne recommande pas de l'utiliser, ce n'est pas bon pour l'utilisateur De plus, n'oubliez pas d'ajouter la vérification de version iOS. avant que la demande de charge appelle cette fonction.
request?.addCookies()
j'ai écrit l'extension pour 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
}
}
}
}
vous êtes prêt à tester iOS> 8
La meilleure solution pour les requêtes XHR est montrée ici
Version 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)
}
Cela fonctionne pour moi: Après setcookies, ajoutez 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))
}
})
}
Lorsque vous ajoutez plusieurs éléments de cookie, vous pouvez procéder comme suit: (path
& domain
est requis pour chaque élément)
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];
sinon, seul le premier cookie sera défini.
Vous pouvez également utiliser WKWebsiteDataStore pour obtenir un comportement similaire à HTTPCookieStorage à partir de UIWebView.
let dataStore = WKWebsiteDataStore.default()
let cookies = HTTPCookieStorage.shared.cookies ?? [HTTPCookie]()
cookies.forEach({
dataStore.httpCookieStore.setCookie($0, completionHandler: nil)
})
Si quelqu'un utilise Alamofire, c'est la meilleure solution.
let cookies = Alamofire.SessionManager.default.session.configuration.httpCookieStorage?.cookies(for: URL(string: BASE_URL)!)
for (cookie) in cookies ?? [] {
webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
}
J'ai essayé toutes les réponses ci-dessus mais aucune d'entre elles ne fonctionne. Après tant de tentatives, j'ai finalement trouvé un moyen fiable de configurer le cookie WKWebview.
Tout d'abord, vous devez créer une instance de WKProcessPool et la définir sur WKWebViewConfiguration à utiliser pour initialiser le WkWebview lui-même:
private lazy var mainWebView: WKWebView = {
let webConfiguration = WKWebViewConfiguration()
webConfiguration.processPool = WKProcessPool()
let webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = self
return webView
}()
Définir WKProcessPool est l'étape la plus importante ici. WKWebview utilise l'isolation de processus, ce qui signifie qu'il s'exécute sur un processus différent de celui de votre application. Cela peut parfois provoquer des conflits et empêcher la synchronisation de votre cookie avec WKWebview.
Regardons maintenant la définition de WKProcessPool
Le pool de processus associé à une vue Web est spécifié par sa configuration de vue Web. Chaque affichage Web se voit attribuer son propre processus de contenu Web jusqu'à ce qu'une limite de processus définie par l'implémentation soit atteinte; après cela, les vues Web avec le même pool de processus finissent par partager les processus de contenu Web.
Faites attention à la dernière phrase si vous prévoyez d'utiliser le même WKWebview pour les requêtes de sous-séquence
les vues Web avec le même pool de processus finissent par partager le contenu Web les processus
cela signifie que si vous n'utilisez pas la même instance de WKProcessPool à chaque fois que vous configurez un WKWebView pour le même domaine (vous avez peut-être un VC A qui contient un WKWebView et vous souhaitez créer différentes instances de VC A à différents endroits), il peut y avoir des cookies de paramétrage de conflit. Pour résoudre le problème, après la première création du WKProcessPool pour un WKWebView qui charge le domaine B, je l’enregistre dans un singleton et utilise le même WKProcessPool chaque fois que je crée un WKWebView qui charge le même domaine 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
}()
Après le processus d'initialisation, vous pouvez charger un URLRequest à l'intérieur du bloc d'achèvement de httpCookieStore.setCookie
. Ici, vous devez joindre le cookie à l'en-tête de la demande, sinon il ne fonctionnera pas.
P/s: J'ai volé l'extension de la réponse fantastique ci-dessus par 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)
}
}