WKWebViewが使用するCookieを設定できますか?


135

既存のアプリをからUIWebViewに切り替えようとしていWKWebViewます。現在のアプリはの外でユーザーのログイン/セッションを管理し、認証に必要なをにwebview設定cookiesNSHTTPCookieStoreます。残念ながらnew WKWebViewcookiesfromを使用しませんNSHTTPCookieStorage。これを達成する別の方法はありますか?

回答:


186

iOS 11以降のみの編集

WKHTTPCookieStoreを使用します

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

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

HTTPCookeStorageからそれらをプルしているので、これを行うことができます:

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

iOS 10以前の古い回答

最初のロード要求でCookieを設定する必要がある場合は、NSMutableURLRequestで設定できます。Cookieは特別にフォーマットされたリクエストヘッダーにすぎないため、次のように実現できます。

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

ページの後続のAJAXリクエストでCookieを設定する必要がある場合は、WKUserScriptを使用して、ドキュメントの開始時に次のようにjavascriptを介してプログラムで値を設定するだけで実現できます。

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

これら2つの手法を組み合わせると、Cookieの値をネイティブアプリランドからWebビューランドに転送するのに十分なツールが得られます。さらに高度なCookieが必要な場合は、MozillaのページでCookieのJavaScript APIに関する詳細をご覧ください

ええ、AppleがUIWebViewの多くの優れた点をサポートしていないのは残念です。彼らが彼らをサポートするかどうかはわかりませんが、うまくいけば彼らはすぐにこれに乗り込むでしょう。お役に立てれば!


1
後続のリクエストのためにCookieを挿入するのに最適な場所はどこですか?たとえば、最初のページの読み込みは上記の回答でカバーされていますが、同じドメインにつながるリンクがページにあり、リクエストに同じCookieを挿入する必要がある場合はどうなりますか?didStartProvisionalNavigation?
メイソンG.ズウィティ2015年

1
申し訳ありませんが、機能しません。私の考えでは、ドメインが同じであれば問題はないはずです。リンクがリクエストのロード元と同じドメインを指していることをコードでダブルチェックできますか?また、Cookieを特定の「パス」に制限することもできます。多分それがいくつかの問題を引き起こしていますか?
mattr 2015年

11
Cookieを設定するJavaScriptテクニックは、「HTTPのみ」のCookieでは機能しないことに注意してください。
Ahmed Nasser

1
上記の方法はうまくいきます...しかし、後続のAJAX呼び出しでCookieが重複して表示される可能性があります(1度だけ重複)。
Durga Vundavalli 2016年

1
@ Axel92Dev回避策は、WebViewからサーバーに対して行われた最初のリクエストが、HTTPOnlyフラグを使用してCookieを再度設定するようにWebViewに明示的に指示する応答(つまり、応答でCookieを再度設定する)を確実に取得することです。Webviewを初期化するときに、その唯一の目的のために特別なAPIを作成し、正常にWebviewを使用できます。
アーメドナセル

63

この回答で遊んだ後(これは非常に役に立ちました:)私たちはいくつかの変更を加える必要がありました:

  • ドメイン間でプライベートCookie情報を漏らさずに複数のドメインを処理するためのWebビューが必要です
  • 安全なクッキーを尊重するために必要です
  • サーバーがCookieの値を変更した場合、アプリでCookieの値を知る必要があります NSHTTPCookieStorage
  • サーバーがCookieの値を変更した場合、リンクやAJAXなどをたどったときにスクリプトがその値を元の値にリセットしないようにします。

そのため、コードを次のように変更しました。

リクエストを作成する

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

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

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

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

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

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

// Now perform the request...

これにより、共有ストレージから他のドメイン用のCookieを送信したり、安全なCookieを安全でないリクエストに送信したりせずに、最初のリクエストに正しいCookieが設定されます。

さらなるリクエストへの対応

また、他のリクエストにCookieが設定されていることを確認する必要もあります。これは、ドキュメントのロード時に実行されるスクリプトを使用して行われます。このスクリプトは、Cookieが設定されているかどうかを確認し、設定されていない場合はの値に設定しNSHTTPCookieStorageます。

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

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

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

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

...

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

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

Cookieの変更への対処

また、Cookieの値を変更するサーバーにも対処する必要があります。つまり、を更新するために作成しているWebビューからコールバックする別のスクリプトを追加しますNSHTTPCookieStorage

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

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

デリゲートメソッドを実装して変更されたCookieを更新し、現在のドメインのCookieのみが更新されるようにします。

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

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

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

これにより、WKWebViewを使用する場所ごとに異なる方法で対処しなくても、Cookieの問題が解決するようです。これで、このコードをヘルパーとして使用してWebビューを作成し、透過的に更新NSHTTPCookieStorageすることができます。


編集:私はNSHTTPCookieでプライベートカテゴリを使用していることがわかりました-これがコードです:

- (NSString *)wn_javascriptString {
    NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",
                        self.name,
                        self.value,
                        self.domain,
                        self.path ?: @"/"];

    if (self.secure) {
        string = [string stringByAppendingString:@";secure=true"];
    }

    return string;
}

6
あなたのコードをWKWebViewのサブクラスにラップしました。それをチェックアウトしてお気軽github.com/haifengkao/YWebView
ハイ風水花王

Cookieに=記号が含まれている場合はどうなりますか これはうまくいくでしょうか?
iOS Addicted

@iOSAddictedそう思います。あなたの値がa=bあなたであったなら、あなたはクッキー文字列で終わるでしょうname=a=b;domain=.example.com;path=/-私は標準が分割し;、次にkey = valueペアの最初のもの =で分割すると信じています。私もこれをテストします:)
deanWombourne

あなたの応答は私を助けてくれました、しかし私はあなたの投稿に何かを追加したいです、あなたの更新メソッドを使用するときいくつかのリスクがあります、いくつかのJSフレームワークは同じ名前でドメインが異なるクッキーを作成するかもしれません、そしてあなたがそれを更新しようとした場合jsメソッドを使用すると、Cookieを誤った値で更新するリスクが高くなります。また、私たちのjs cookie文字列は、安全なフラグを取り除く必要がありました。サーバーがhttpとhttpsの間で厄介なリダイレクトを行うため、一部の厄介なエッジケースでは、安全なcookieが一部のページに存在しないためです。
RicardoDuarte 2018

私が実際にこれを書いたときに一緒にいた会社は、それが稼働した後にそれにいくつかのドメイン保護を追加しなければならなかったと思います。安全/安全でない問題に(afaik)遭遇したことはありません-悪夢のように聞こえます!
deanWombourne、2018

42

WKWebView作成する前に、構成でCookieを設定する必要があります。それ以外の場合は、さえでWKHTTPCookieStoresetCookie完了ハンドラ、クッキーは確実にWeb表示に同期されません。これは上のドキュメントからこの行に戻りますWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

それ @NSCopyingはやや深いコピーです。実装は私を超えていますが、最終結果は、webviewを初期化する前にcookieを設定しない限り、そこにあるcookieを信頼できません。ビューの初期化は非同期プロセスになるため、アプリのアーキテクチャが複雑になる可能性があります。あなたはこのようなものになるでしょう

extension WKWebViewConfiguration {
    /// Async Factory method to acquire WKWebViewConfigurations packaged with system cookies
    static func cookiesIncluded(completion: @escaping (WKWebViewConfiguration?) -> Void) {
        let config = WKWebViewConfiguration()
        guard let cookies = HTTPCookieStorage.shared.cookies else {
            completion(config)
            return
        }
        // Use nonPersistent() or default() depending on if you want cookies persisted to disk
        // and shared between WKWebViews of the same app (default), or not persisted and not shared
        // across WKWebViews in the same app.
        let dataStore = WKWebsiteDataStore.nonPersistent()
        let waitGroup = DispatchGroup()
        for cookie in cookies {
            waitGroup.enter()
            dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
        }
        waitGroup.notify(queue: DispatchQueue.main) {
            config.websiteDataStore = dataStore
            completion(config)
        }
    }
}

そしてそれを次のように使う

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

上記の例は、可能な限り最後までビューの作成を延期します。別の解決策は、configまたはwebviewを事前に作成し、非同期の性質を処理してからビューコントローラーを作成することです。

最後の注意:このWebビューを作成した後、それを実際に使用するように設定すると、この回答で説明されている方法を使用せずにCookieを追加することはできません。ただし、WKHTTPCookieStoreObserverAPIを使用して、少なくともCookieに発生する変更を監視できます。したがって、セッションCookieがWebViewで更新されたHTTPCookieStorage場合、必要に応じて、この新しいCookieでシステムを手動で更新できます。

詳しくは、2017年のWWDCセッションのカスタムWebコンテンツの読み込みで 18:00までスキップしてください。このセッションの始めに、webviewが完了ハンドラーで作成されるべきであるという事実を省略している不正なコードサンプルがあります。

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

18:00のライブデモはこれを明らかにします。

編集少なくともMojaveベータ7およびiOS 12ベータ7以降、Cookieでの動作の一貫性が大幅に向上しています。setCookie(_:)この方法は、さらに後にクッキーを設定できるように表示されますWKWebView作成されています。私がする、それが重要かかわらを見つけた触れないでprocessPoolすべての変数を。Cookie設定機能は、追加のプールが作成されておらず、そのプロパティがそのままにされている場合に最適に機能します。WebKitのいくつかのバグが原因で問題が発生していたと言っても差し支えないと思います。


Mojave 10.14ベータ3およびiOS 12ベータ3でCookieの処理/設定がより信頼できるように見えます
nteissler '12 / 07/18

6
非常に深さと過小評価の答えで
ニコラス・カラスコ

1
すでにロードされているWKWebViewを使用して、iOS 12でこの問題がまだ発生しています。setCookie()は実際にはすぐにWKWebViewと同期される場合もあれば、散発的に処理されない場合もあります
bmjohns

レーダーが修正されたと主張されて以来、私もまだ問題を見てきましたが、それほど頻繁ではありません。どのくらいの頻度でCookieの失敗が発生しますか?あなたが再現可能な、十分に小さいプロジェクトを持っている場合は、私は本当にここWebKitのバグを提出するお勧めします:webkit.org/reporting-bugsあなたはまたつぶやくことができますブレイディEidsonこれらの種類に非常に敏感である(うまく)アップルのWebKitの建築家をレポートとバグ。
nteissler 2018

これが正解です。各URLRequestのヘッダーフィールドとしてCookieを手動で変換する必要はありません。ここで説明するように、setCookie()を使用する必要があるだけです。
ギヨームローラン

25

私のために働く

func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
    let headerFields = navigationAction.request.allHTTPHeaderFields
    var headerIsPresent = contains(headerFields?.keys.array as! [String], "Cookie")

    if headerIsPresent {
        decisionHandler(WKNavigationActionPolicy.Allow)
    } else {
        let req = NSMutableURLRequest(URL: navigationAction.request.URL!)
        let cookies = yourCookieData
        let values = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies)
        req.allHTTPHeaderFields = values
        webView.loadRequest(req)

        decisionHandler(WKNavigationActionPolicy.Cancel)
    }
}

素晴らしいハック、特に、非透過的なiOSが既存のWKWebViewのCookieをオーバーライドする方法について。唯一の問題は、以前のWKNavigationKeyが古くなっていることです。他のコードが古いコードを無駄に待っているかもしれません。
BaseZen 2017

2
これは正しいです?状況によってはうまくいくことを感謝します。ただし、このデリゲートメソッド(decidePolicyForNavigationAction)の責任は、ポリシーを決定することです。実際にリクエストをロードしないこと。それは以前に始められました。どの場合、これによりリクエストが2回ロードされることはありませんか?
Max MacLeod 2017

2
@MaxMacLeod else条件で彼はdecisionHandlerクロージャを呼び出している.cancelので、webviewは実際には初期リクエストをロードしません。条件でloadRequestが呼び出された後、elseこのデリゲートメソッドはその要求に対して再度呼び出されifCookieヘッダーが存在するため条件に入れられます。
halil_g 2017

2
ただし、最初のリクエストにすでにいくつかのCookieが設定されている場合は、このelse状態になることはないため、これは機能しません。
halil_g 2017

これは1)すべての状況で機能しないことに注意してください-たとえば、Webビューがフレームをロードする場合など2)安全ではありません-機密情報を含むCookieをサードパーティのURLに送信できます
Peter Prokop

20

HTTPCookieStorageからすべてのCookieを注入するためのSwift のMattrsソリューションの私のバージョンは次のとおりです。これは、主に認証Cookieを挿入してユーザーセッションを作成するために行われました。

public func setupWebView() {
    let userContentController = WKUserContentController()
    if let cookies = HTTPCookieStorage.shared.cookies {
        let script = getJSCookiesString(for: cookies)
        let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
        userContentController.addUserScript(cookieScript)
    }
    let webViewConfig = WKWebViewConfiguration()
    webViewConfig.userContentController = userContentController

    self.webView = WKWebView(frame: self.webViewContainer.bounds, configuration: webViewConfig)
}

///Generates script to create given cookies
public func getJSCookiesString(for cookies: [HTTPCookie]) -> String {
    var result = ""
    let dateFormatter = DateFormatter()
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"

    for cookie in cookies {
        result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
        if let date = cookie.expiresDate {
            result += "expires=\(dateFormatter.stringFromDate(date)); "
        }
        if (cookie.secure) {
            result += "secure; "
        }
        result += "'; "
    }
    return result
}

良くわからロケールの書式が正しいことにするために、この行を追加する:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu

どこに電話するの?
markhorrocks 2017年

(マイナーのadjustements付き)スウィフト4で私のためにこの加工した罰金
フレデリック・ダッダ

これは私にとってはうまくいきますが、私がサイトにアクセスするのは2回目(初めてCookieが設定されていないとき)だけです-誰かがこれに遭遇しましたか?
MrChrisBarker

最初の読み込みでエラーが発生し、2番目の読み込みで問題が発生する:(問題の原因は何ですか?
Shauket Sheikh 2018

10

クッキーを設定する

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

クッキーを削除

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

9

Swift 3アップデート:

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    if let urlResponse = navigationResponse.response as? HTTPURLResponse,
       let url = urlResponse.url,
       let allHeaderFields = urlResponse.allHeaderFields as? [String : String] {
       let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHeaderFields, for: url)
       HTTPCookieStorage.shared.setCookies(cookies , for: urlResponse.url!, mainDocumentURL: nil)
       decisionHandler(.allow)
    }
}

1
こんにちは、Cookieを取得するコードも追加できますHTTPCookieStorage.sharedか?
markhorrocks 2017年

これは、私がWKWebViewを使用して、WebビューからのすべてのリクエストにCookieを追加する唯一の方法です
チコヴィッツ

応答にhttponly cookieが含まれている場合、この方法でcookie値を取得できませんでした。

1
これは、cookieの設定をhttpcookiesストレージに戻すだけです。ここで、wkwebviewのCookieを設定したコードはどこにありますか?
Shauket Sheikh 2018

8

ここでさまざまな回答を調べても成功しなかった後、WebKitのドキュメントを調べて、cookieの配列をヘッダーフィールドに適した形式に変換するrequestHeaderFields上の静的メソッドを見つけましたHTTPCookie。これとCookieヘッダーを読み込む前にを更新するというmattrの洞察を組み合わせるとURLRequest、フィニッシュラインを通過することができました。

Swift 4.1、4.2、5.0:

var request = URLRequest(url: URL(string: "https://example.com/")!)
let headers = HTTPCookie.requestHeaderFields(with: cookies)
for (name, value) in headers {
    request.addValue(value, forHTTPHeaderField: name)
}

let webView = WKWebView(frame: self.view.frame)
webView.load(request)

これをさらに簡単にするには、拡張機能を使用します。

extension WKWebView {
    func load(_ request: URLRequest, with cookies: [HTTPCookie]) {
        var request = request
        let headers = HTTPCookie.requestHeaderFields(with: cookies)
        for (name, value) in headers {
            request.addValue(value, forHTTPHeaderField: name)
        }

        load(request)
    }
}

今では次のようになります:

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

この拡張機能はLionheartExtensionsでも利用できます。、ドロップインソリューションだけが必要な場合。乾杯!


1
@ShauketSheikhうーん、どのような状況でこれが機能しないのですか?
Dan Loewenherz 2018

シミュレータiOS 8を使用してテストしましたが、Cookieを送信していないようです。再確認しました。
Shauket Sheikh 2018

@Danを試すことができる私の回答を投稿しました
Shauket Sheikh

7

iOS 11では、今すぐcookieを管理できます:)。このセッションを参照してください:https : //developer.apple.com/videos/play/wwdc2017/220/

ここに画像の説明を入力してください


2
@ShobhakarTiwariなんで?iOS11正式リリースで何か変更はありますか?
ジャッキー2017

iOS 11以降のみをサポートしている場合、以前のバージョンをサポートする必要がある場合は、ページをロードする前にJavaScriptを使用するのが最善の方法です。
PashaN 2018年

これは私にとっては機能しますが、setcookieメソッドが完了ハンドラを実行しない場合があります。つまり、Webページが読み込まれないことがあります。デバイスでのみ発生し、3回目、4回目、5回目で終了して再開します。 WebView、そしてそれが一度起こった後、私がアプリをリセットするまでそれは起こり続けます-誰かもこれに遭遇しますか?
Binya Koatz 2018年

5

この回答を投稿した理由は、多くの解決策を試したが、誰も正しく機能しなかったためです。ほとんどの回答は、最初にCookieを設定する必要がある場合に機能せず、結果としてCookieが最初に同期されませんでした。この解決策を両方で機能するように使用してくださいiOS> = 11.0 <= iOS 11から8.0まで。Cookieの同期も初めて機能します。

iOSの場合> = 11.0 -Swift 4.2

この方法でhttp cookieを取得し、wkwebview cookieストアに設定します。リクエストをロードするのは非常にトリッキーです。 wkwebview。cookieが完全に設定されるときにロード要求を送信する必要があります。ここに書いた関数があります。

load webviewを呼び出して、完了時にクロージャで関数を呼び出します。FYIこの関数はiOS> = 11.0のみを処理します

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

syncCookies関数の実装は次のとおりです。

func syncCookies(completion:@escaping ()->Void) {

if #available(iOS 11.0, *) {

      if let yourCookie = "HERE_YOUR_HTTP_COOKIE_OBJECT" {
        self.configuration.websiteDataStore.httpCookieStore.setCookie(yourCookie, completionHandler: {
              completion()
        })
     }
  } else {
  //Falback just sent 
  completion()
}
}

iOS 8からiOS 11まで

WKUserScriptを使用して、2つのタイムCookieを1つ設定するために必要な追加の設定を行う必要があります。、リクエストでCookieを追加することも忘れないでください。そうしないと、Cookieが最初に同期されず、ページが初めて正しく読み込まれないことがわかります。これは、iOS 8.0のCookieをサポートしていることがわかりました

Wkwebviewオブジェクトを作成する前。

func setUpWebView() {

    let userController: WKUserContentController = WKUserContentController.init()

    if IOSVersion.SYSTEM_VERSION_LESS_THAN(version: "11.0") {
        if let cookies = HTTPCookieStorage.shared.cookies {
            if let script = getJSCookiesString(for: cookies) {
                cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
                userController.addUserScript(cookieScript!)
            }
        }
    }

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.processPool = BaseWebViewController.processPool


    webConfiguration.userContentController = userController


    let customFrame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: 0.0, height: self.webContainerView.frame.size.height))
    self.WwebView = WKWebView (frame: customFrame, configuration: webConfiguration)
    self.WwebView.translatesAutoresizingMaskIntoConstraints = false
    self.webContainerView.addSubview(self.WwebView)
    self.WwebView.uiDelegate = self
    self.WwebView.navigationDelegate = self
    self.WwebView.allowsBackForwardNavigationGestures = true // A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations
    self.WwebView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)


 self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .trailing, relatedBy: .equal, toItem: self.webContainerView, attribute: .trailing, multiplier: 1, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .leading, relatedBy: .equal, toItem: self.webContainerView, attribute: .leading, multiplier: 1, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .top, relatedBy: .equal, toItem: self.webContainerView, attribute: .top, multiplier: 1, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .bottom, relatedBy: .equal, toItem: self.webContainerView, attribute: .bottom, multiplier: 1, constant: 0))


}

この関数に焦点を当てますgetJSCookiesString

 public func getJSCookiesString(for cookies: [HTTPCookie]) -> String? {

    var result = ""
    let dateFormatter = DateFormatter()
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"

    for cookie in cookies {
        if cookie.name == "yout_cookie_name_want_to_sync" {
            result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
            if let date = cookie.expiresDate {
                result += "expires=\(dateFormatter.string(from: date)); "
            }
            if (cookie.isSecure) {
                result += "secure; "
            }
            result += "'; "
        }

    }

    return result
}

ここに他のステップwkuserscriptがすぐにcookieを同期しない場合があります。cookieを使用して最初のページをロードするには、プロセスを終了する場合はwebviewを再度リロードする必要がありますが、使用はお勧めしません。ユーザーの観点からはあまり良くありません。このように、リクエストヘッダーにリクエストセットCookieをロードする準備ができているときはいつでも、iOSバージョンチェックを追加することを忘れないでください。ロード要求の前にこの関数を呼び出します。

request?.addCookies()

URLRequestの拡張機能を書いた

extension URLRequest {

internal mutating func addCookies() {
    //"appCode=anAuY28ucmFrdXRlbi5yZXdhcmQuaW9zLXpOQlRTRmNiejNHSzR0S0xuMGFRb0NjbUg4Ql9JVWJH;rpga=kW69IPVSYZTo0JkZBicUnFxC1g5FtoHwdln59Z5RNXgJoMToSBW4xAMqtf0YDfto;rewardadid=D9F8CE68-CF18-4EE6-A076-CC951A4301F6;rewardheader=true"
    var cookiesStr: String = ""

    if IOSVersion.SYSTEM_VERSION_LESS_THAN(version: "11.0") {
        let mutableRequest = ((self as NSURLRequest).mutableCopy() as? NSMutableURLRequest)!
        if let yourCookie = "YOUR_HTTP_COOKIE_OBJECT" {
            // if have more than one cookies dont forget to add ";" at end
            cookiesStr += yourCookie.name + "=" + yourCookie.value + ";"

            mutableRequest.setValue(cookiesStr, forHTTPHeaderField: "Cookie")
            self = mutableRequest as URLRequest

        }
    }

  }
}

これで、iOS> 8をテストする準備ができました


2

箱から出してすぐに動作する可能性が最も高いソリューションを見つけてください。基本的には、Swift 4 @ user3589213回答に合わせて変更および更新されています

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

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

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

        var req = navigationAction.request
        req.allHTTPHeaderFields = headers

        webView.load(req)

        decisionHandler(.cancel)
    }
}

1

上記のすべての答えを試しましたが、どれもうまくいきません。何度も試みた結果、WKWebview Cookieを設定するための信頼できる方法がようやく見つかりました。

まず、WKProcessPoolのインスタンスを作成し、それをWkWebview自体の初期化に使用されるWKWebViewConfigurationに設定する必要があります。

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

WKProcessPoolの設定は、ここで最も重要なステップです。WKWebviewはプロセスの分離を利用します。つまり、アプリのプロセスとは異なるプロセスで実行されます。これにより、競合が発生し、CookieがWKWebviewと正しく同期されなくなることがあります。

次に、WKProcessPoolの定義を見てみましょう

Webビューに関連付けられたプロセスプールは、Webビューの構成で指定されます。各Webビューには、実装定義のプロセス制限に達するまで、独自のWebコンテンツプロセスが与えられます。その後、同じプロセスプールを持つWebビューは、Webコンテンツプロセスを共有することになります。

サブシーケンスリクエストに同じWKWebviewを使用する場合は、最後の文に注意してください

同じプロセスプールを持つWebビューは、最終的にWebコンテンツプロセスを共有します

つまり、同じドメインにWKWebViewを構成するたびにWKProcessPoolの同じインスタンスを使用しない場合(おそらく、WKWebViewを含むVC Aがあり、異なる場所にVC Aの異なるインスタンスを作成したいということです) )、Cookieの設定が競合している可能性があります。この問題を解決するには、ドメインBをロードするWKWebViewのWKProcessPoolを最初に作成した後、それをシングルトンに保存し、同じドメインBをロードするWKWebViewを作成する必要があるたびに同じWKProcessPoolを使用します。

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

初期化プロセスが完了したら、URLRequestのをロードすることができ、完了ブロック内httpCookieStore.setCookie。ここでは、リクエストヘッダーにCookieを添付する必要があります。そうしないと機能しません。

P / s: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)
   }
}

1

私のバージョンのnteissの答え。でテスト済みiOS 11, 12, 13。あなたのようなルックスは、使用する必要はありませんDispatchGroupiOS 13、もう。

私は非静的関数を使用するincludeCustomCookies上でWKWebViewConfiguration、私は更新できるようにすることを、cookies私は新しい作成するたびにWKWebViewConfiguration

extension WKWebViewConfiguration {
    func includeCustomCookies(cookies: [HTTPCookie], completion: @escaping  () -> Void) {
        let dataStore = WKWebsiteDataStore.nonPersistent()
        let waitGroup = DispatchGroup()

        for cookie in cookies {
            waitGroup.enter()
            dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
        }

        waitGroup.notify(queue: DispatchQueue.main) {
            self.websiteDataStore = dataStore
            completion()
        }
    }
}

次に、次のように使用します。

let customUserAgent: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15"

let customCookies: [HTTPCookie] = {
    let cookie1 = HTTPCookie(properties: [
        .domain: "yourdomain.com",
        .path: "/",
        .name: "auth_token",
        .value: APIManager.authToken
    ])!

    let cookie2 = HTTPCookie(properties: [
        .domain: "yourdomain.com",
        .path: "/",
        .name: "i18next",
        .value: "ru"
    ])!

    return [cookie1, cookie2]
}()

override func viewDidLoad() {
    super.viewDidLoad()

    activityIndicatorView.startAnimating()

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.includeCustomCookies(cookies: customCookies, completion: { [weak self] in
        guard let strongSelf = self else { return }
        strongSelf.webView = WKWebView(frame: strongSelf.view.bounds, configuration: webConfiguration)
        strongSelf.webView.customUserAgent = strongSelf.customUserAgent
        strongSelf.webView.navigationDelegate = strongSelf
        strongSelf.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        strongSelf.view.addSubview(strongSelf.webView)
        strongSelf.view.bringSubviewToFront(strongSelf.activityIndicatorView)
        strongSelf.webView.load(strongSelf.request)
    })
}

0

XHRリクエストのより良い修正はここに示されています

Swift 4バージョン:

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Swift.Void) {
    guard
        let response = navigationResponse.response as? HTTPURLResponse,
        let url = navigationResponse.response.url
    else {
        decisionHandler(.cancel)
        return
    }

    if let headerFields = response.allHeaderFields as? [String: String] {
        let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url)
        cookies.forEach { (cookie) in
            HTTPCookieStorage.shared.setCookie(cookie)
        }
    }

    decisionHandler(.allow)
}

0

誰かがAlamofireを使用しているなら、これはより良い解決策です。

  let cookies = Alamofire.SessionManager.default.session.configuration.httpCookieStorage?.cookies(for: URL(string: BASE_URL)!)
  for (cookie) in cookies ?? [] {
      webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
  }

0

これは私のために働きます:setcookiesの後に、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))
                        }

                    })
                }

0

乗算Cookieアイテムを追加するときは、次のようにすることができます(各アイテムにはpathdomainが必要です)

NSString *cookie = [NSString stringWithFormat:@"document.cookie = 'p1=%@;path=/;domain=your.domain;';document.cookie = 'p2=%@;path=/;domain=your.domain;';document.cookie = 'p3=%@;path=/;domain=your.domain;';", p1_string, p2_string, p3_string];

WKUserScript *cookieScript = [[WKUserScript alloc]
            initWithSource:cookie
            injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];

[userContentController addUserScript:cookieScript];

それ以外の場合は、最初のCookieアイテムのみが設定されます。


0

WKWebsiteDataStoreを使用して、UIWebViewからHTTPCookieStorageと同様の動作を取得することもできます。

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

0

以下のコードは、私のプロジェクトSwift5でうまく機能しています。以下のWKWebViewでURLをロードしてみてください。

    private func loadURL(urlString: String) {
        let url = URL(string: urlString)
        guard let urlToLoad = url else { fatalError("Cannot find any URL") }

        // Cookies configuration
        var urlRequest = URLRequest(url: urlToLoad)
        if let cookies = HTTPCookieStorage.shared.cookies(for: urlToLoad) {
            let headers = HTTPCookie.requestHeaderFields(with: cookies)
            for header in headers { urlRequest.addValue(header.value, forHTTPHeaderField: header.key) }
        }

        webview.load(urlRequest)
    }

0

これは、iOS 9以降でCookieとWKWebViewを処理するための私のソリューションです。

import WebKit

extension WebView {

    enum LayoutMode {
        case fillContainer
    }

    func autoLayout(_ view: UIView?, mode: WebView.LayoutMode = .fillContainer) {
        guard let view = view else { return }
        self.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(self)

        switch mode {
        case .fillContainer:
                NSLayoutConstraint.activate([
                self.topAnchor.constraint(equalTo: view.topAnchor),
                self.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                self.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                self.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            ])
        }
    }

}

class WebView : WKWebView {

    var request : URLRequest?

    func load(url: URL, useSharedCookies: Bool = false) {
        if useSharedCookies, let cookies = HTTPCookieStorage.shared.cookies(for: url) {
            self.load(url: url, withCookies: cookies)
        } else {
            self.load(URLRequest(url: url))
        }
    }

    func load(url: URL, withCookies cookies: [HTTPCookie]) {
        self.request = URLRequest(url: url)
        let headers = HTTPCookie.requestHeaderFields(with: cookies)
        self.request?.allHTTPHeaderFields = headers
        self.load(request!)
    }

}

0

私が行っていたこの間違いは、ドメイン属性でURL全体を渡していたため、ドメイン名のみである必要があります。

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

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.