159

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

4

24 に答える 24

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)
    }
}
于 2015-08-25T05:58:58.027 に答える
19

これは、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
}
于 2015-10-13T14:41:03.390 に答える
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()
}
于 2015-12-15T03:52:32.207 に答える
9

スウィフト 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)
    }
}
于 2017-01-12T23:13:49.447 に答える
8

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

スウィフト 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でも利用できます。乾杯!

于 2018-05-30T20:26:57.470 に答える
7

iOS 11 では、Cookie を管理できるようになりました:)、このセッションを参照してください: https://developer.apple.com/videos/play/wwdc2017/220/

ここに画像の説明を入力

于 2017-06-22T05:31:33.123 に答える
2

上記のすべての回答を試しましたが、どれも機能しません。何度も試みた結果、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)
   }
}
于 2019-01-15T02:49:26.253 に答える
2

iOS 10 以降のソリューション

詳細

  • スイフト5.1
  • Xcode 11.6 (11E708)

解決

import UIKit
import WebKit
extension WKWebViewConfiguration {
    func set(cookies: [HTTPCookie], completion: (() -> Void)?) {
        if #available(iOS 11.0, *) {
            let waitGroup = DispatchGroup()
            for cookie in cookies {
                waitGroup.enter()
                websiteDataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
            }
            waitGroup.notify(queue: DispatchQueue.main) { completion?() }
        } else {
            cookies.forEach { HTTPCookieStorage.shared.setCookie($0) }
            self.createCookiesInjectionJS(cookies: cookies) {
                let script = WKUserScript(source: $0, injectionTime: .atDocumentStart, forMainFrameOnly: false)
                self.userContentController.addUserScript(script)
                DispatchQueue.main.async { completion?() }
            }
        }
    }

    private func createCookiesInjectionJS (cookies: [HTTPCookie],  completion: ((String) -> Void)?) {
        var scripts: [String] = ["var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } )"]
        let now = Date()

        for cookie in cookies {
            if let expiresDate = cookie.expiresDate, now.compare(expiresDate) == .orderedDescending { continue }
            scripts.append("if (cookieNames.indexOf('\(cookie.name)') == -1) { document.cookie='\(cookie.javaScriptString)'; }")
        }
        completion?(scripts.joined(separator: ";\n"))
    }
}

extension WKWebView {
    func loadWithCookies(request: URLRequest) {
        if #available(iOS 11.0, *) {
            load(request)
        } else {
            var _request = request
            _request.setCookies()
            load(_request)
        }
    }
}

extension URLRequest {

    private static var cookieHeaderKey: String { "Cookie" }
    private static var noAppliedcookieHeaderKey: String { "No-Applied-Cookies" }

    var hasCookies: Bool {
        let headerKeys = (allHTTPHeaderFields ?? [:]).keys
        var hasCookies = false
        if headerKeys.contains(URLRequest.cookieHeaderKey) { hasCookies = true }
        if !hasCookies && headerKeys.contains(URLRequest.noAppliedcookieHeaderKey) { hasCookies = true }
        return hasCookies
    }

    mutating func setCookies() {
        if #available(iOS 11.0, *) { return }
        var cookiesApplied = false
        if let url = self.url, let cookies = HTTPCookieStorage.shared.cookies(for: url) {
            let headers = HTTPCookie.requestHeaderFields(with: cookies)
            for (name, value) in headers { setValue(value, forHTTPHeaderField: name) }
            cookiesApplied = allHTTPHeaderFields?.keys.contains(URLRequest.cookieHeaderKey) ?? false
        }
        if !cookiesApplied { setValue("true", forHTTPHeaderField: URLRequest.noAppliedcookieHeaderKey) }
    }
}

/// https://github.com/Kofktu/WKCookieWebView/blob/master/WKCookieWebView/WKCookieWebView.swift
extension HTTPCookie {

    var javaScriptString: String {
        if var properties = properties {
            properties.removeValue(forKey: .name)
            properties.removeValue(forKey: .value)

            return properties.reduce(into: ["\(name)=\(value)"]) { result, property in
                result.append("\(property.key.rawValue)=\(property.value)")
            }.joined(separator: "; ")
        }

        var script = [
            "\(name)=\(value)",
            "domain=\(domain)",
            "path=\(path)"
        ]

        if isSecure { script.append("secure=true") }

        if let expiresDate = expiresDate {
            script.append("expires=\(HTTPCookie.dateFormatter.string(from: expiresDate))")
        }

        return script.joined(separator: "; ")
    }

    private static let dateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US")
        dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
        dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
        return dateFormatter
    }()
}

使用法

ここにソリューションコードを貼り付けることを忘れないでください

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

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

extension WebViewController: WKNavigationDelegate {

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

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

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

完全なサンプル

ここにソリューションコードを貼り付けることを忘れないでください

import UIKit
import WebKit

class ViewController: UIViewController {

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

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

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

extension ViewController: WKNavigationDelegate {

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

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

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

Info.plist

Info.plist トランスポート セキュリティ設定を追加します

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
于 2020-08-27T17:13:50.070 に答える
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)
    }
于 2019-09-17T06:44:39.830 に答える