UIWebView
既存のアプリを からに切り替えようとしていWKWebView
ます。現在のアプリは、ユーザーのログイン/セッションを の外部で管理し、認証に必要な を にwebview
設定します。残念ながら newはfrom を使用しません。これを達成する別の方法はありますか?cookies
NSHTTPCookieStore
WKWebView
cookies
NSHTTPCookieStorage
24 に答える
私のために働く
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)
}
}
これは、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
}
クッキーを設定
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()
}
スウィフト 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)
}
}
ここでさまざまな回答を調べて成功しなかった後、WebKit のドキュメントをくまなく調べたところ、cookie の配列をヘッダー フィールドに適した形式に変換するrequestHeaderFields
on の静的メソッドに出くわしました。これを、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でも利用できます。乾杯!
iOS 11 では、Cookie を管理できるようになりました:)、このセッションを参照してください: https://developer.apple.com/videos/play/wwdc2017/220/
上記のすべての回答を試しましたが、どれも機能しません。何度も試みた結果、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)
}
}
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>
以下のコードは、私のプロジェクト 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)
}