WKWebView 不使用 URLCache
WKWebView doesn't use URLCache
我有一个应用程序使用 UIWebView
来显示一些本地内容。我还有一个自定义 URLCache
来拦截请求并用本地内容替换它们:
class LocalWebViewCache: URLCache {
open override func cachedResponse(for request: URLRequest) -> CachedURLResponse? {
if url.absoluteString.range(of: "http://localhost") != nil {
return cacheDelegate?.handleRequest(request)
}
return nil
}
}
我在 AppDelagate
中将此缓存注册为:
var cache: LocalWebViewCache = LocalWebViewCache(memoryCapacity: 20 * 1024 * 1024,
diskCapacity: 100 * 1024 * 1024,
diskPath: nil)
URLCache.shared = cache
目前一切正常。 BUT 苹果拒绝了新版本的应用程序,因为我正在使用 UIWebView
并且它已被弃用,所以我用 WKWebView
替换了 UIWebView
似乎该 webkit 不支持共享缓存 (URLCache.shared
)。
我尝试用 URLProtocol
和 WKURLSchemeHandler
拦截 WKWebView
请求,但没有成功。
如有任何帮助或建议,我将不胜感激。
URLProtocol
代码:
在Appdelagate
:
URLProtocol.registerClass(MyProtocol.self)
class MyProtocol: URLProtocol {
override class func canInit(with request: URLRequest) -> Bool {
print("canInit: \(String(describing: request.url))")
return true
}
override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
print("init: \(String(describing: request.url))")
super.init(request: request, cachedResponse: cachedResponse, client: client)
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
print("canonicalRequest: \(String(describing: request.url))")
return request
}
override func startLoading() {
print("startLoading: \(String(describing: request.url))")
}
}
在所有这些方法中,只有 canInit(with request: URLRequest)
被调用,这不是很有帮助。
WKURLSchemeHandler
代码:
class MySchemeHandler: NSObject, WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
print("start: \(String(describing: urlSchemeTask.request.url?.absoluteString))")
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
print("stop: \(String(describing: urlSchemeTask.request.url?.absoluteString))")
}
}
webkit.configuration.setURLSchemeHandler(MySchemeHandler(), forURLScheme: "localhost")
webkit.load(URLRequest(url: URL(string: "localhost://google.com")!))
但是 webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask)
和 webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask)
都没有被调用。
编辑 1:通过在 url 开头附加 localhost://
加载本地资源并通过 WKURLSchemeHandler
处理它:
let testStr = """
<!DOCTYPE html>
<html>
<head>
<title></title>
<link href="localhost://../styles/style.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<p class="test_class1">This is a paragraph.</p>
<p class ="test_class2">This is another paragraph.</p>
</body>
</html>
"""
lazy var webView: WKWebView = {
let webConfiguration = WKWebViewConfiguration()
webConfiguration
.setURLSchemeHandler(MySchemeHandler(), forURLScheme: "localhost")
let webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.translatesAutoresizingMaskIntoConstraints = false
return webView
}()
webView.loadHTMLString(testStr, baseURL: URL(string: "localhost"))
class MySchemeHandler: NSObject, WKURLSchemeHandler {
let testCss = """
@font-face{
font-family: 'Special';
font-weight: normal;
font-style: normal;
src: url(../fonts/special.ttf);
}
.test_class1 {
font-weight: bold;
color: #007D6E;
font-family: 'Special' !important;
text-align: center !important;
}
.test_class2 {
font-weight: bold;
color: #FF7D6E;
text-align: center !important;
}
"""
func webView(_: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
print("start: \(String(describing: urlSchemeTask.request.url?.absoluteString))")
guard let url = urlSchemeTask.request.url else {
return
}
if url.absoluteString.contains("style.css") {
let data = Data(testCss.utf8)
urlSchemeTask.didReceive(URLResponse(url: url,
mimeType: "text/css",
expectedContentLength: data
.count,
textEncodingName: nil))
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
}
func webView(_: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
print("stop: \(String(describing: urlSchemeTask.request.url?.absoluteString))")
}
}
在日志中我可以看到 start
方法被本地资源触发:
start: Optional("localhost://../styles/style.css")
start: Optional("localhost://../fonts/special.ttf")
但是如果我从 <link href="localhost://../styles/style.css" type="text/css" rel="stylesheet"/>
中删除 localhost://
start
方法根本不会被触发。
据我了解自 iOS 10 WKWebview 支持 AppCache
试试这个代码
let webview: WKWebView
let config = WKWebViewConfiguration()
config.setValue(true, forKey: "_setOfflineApplicationCacheIsEnabled")
webview = WKWebView(frame: .zero, configuration: config)
WKNavigationDelegate
有一个函数来决定如何导航到一个页面,你可以在这里做过滤和重定向。像这样:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// you may need to use .linkActivated depending on how users are navigating
if navigationAction.navigationType == .other {
if let request = navigationAction.request.url {
if request.absoluteString.hasPrefix("https://somePrefix") {
// for test
// guard let localRedirect = URL.init(string: "https://google.com") else {
guard let localRedirect = URL.init(string: "localhost://someUrl") else {
decisionHandler(.allow)
return
}
decisionHandler(.cancel)
// for test
// webView.load(URLRequest.init(url: URL.init(string: "https://www.google.com")!))
webView.loadFileURL(localRedirect, allowingReadAccessTo: localRedirect.deletingLastPathComponent()) // delete last path component if you have other things that the page will load in that directory, like images or css
return
}
}
}
decisionHandler(.allow)
}
I've commented out navigating to google as a test, but left the code there so people can see easily how a redirect would work.
对调用 WKWebView.configuration
的结果设置处理程序无效,因为正在返回副本。相反,您应该创建一个 WKWebViewConfiguration
的新实例,在其上设置您的处理程序,然后将其传递给 WKWebView.init(frame:, configuration:)
:
self.schemeHandler = MySchemeHandler()
self.webview = WKWebView(frame: .zero, configuration: {
let webConfiguration = WKWebViewConfiguration()
webConfiguration.setURLSchemeHandler(self.schemeHandler,
forURLScheme: "localhost")
return webConfiguration
}())
我有一个应用程序使用 UIWebView
来显示一些本地内容。我还有一个自定义 URLCache
来拦截请求并用本地内容替换它们:
class LocalWebViewCache: URLCache {
open override func cachedResponse(for request: URLRequest) -> CachedURLResponse? {
if url.absoluteString.range(of: "http://localhost") != nil {
return cacheDelegate?.handleRequest(request)
}
return nil
}
}
我在 AppDelagate
中将此缓存注册为:
var cache: LocalWebViewCache = LocalWebViewCache(memoryCapacity: 20 * 1024 * 1024,
diskCapacity: 100 * 1024 * 1024,
diskPath: nil)
URLCache.shared = cache
目前一切正常。 BUT 苹果拒绝了新版本的应用程序,因为我正在使用 UIWebView
并且它已被弃用,所以我用 WKWebView
替换了 UIWebView
似乎该 webkit 不支持共享缓存 (URLCache.shared
)。
我尝试用 URLProtocol
和 WKURLSchemeHandler
拦截 WKWebView
请求,但没有成功。
如有任何帮助或建议,我将不胜感激。
URLProtocol
代码:
在Appdelagate
:
URLProtocol.registerClass(MyProtocol.self)
class MyProtocol: URLProtocol {
override class func canInit(with request: URLRequest) -> Bool {
print("canInit: \(String(describing: request.url))")
return true
}
override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
print("init: \(String(describing: request.url))")
super.init(request: request, cachedResponse: cachedResponse, client: client)
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
print("canonicalRequest: \(String(describing: request.url))")
return request
}
override func startLoading() {
print("startLoading: \(String(describing: request.url))")
}
}
在所有这些方法中,只有 canInit(with request: URLRequest)
被调用,这不是很有帮助。
WKURLSchemeHandler
代码:
class MySchemeHandler: NSObject, WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
print("start: \(String(describing: urlSchemeTask.request.url?.absoluteString))")
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
print("stop: \(String(describing: urlSchemeTask.request.url?.absoluteString))")
}
}
webkit.configuration.setURLSchemeHandler(MySchemeHandler(), forURLScheme: "localhost")
webkit.load(URLRequest(url: URL(string: "localhost://google.com")!))
但是 webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask)
和 webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask)
都没有被调用。
编辑 1:通过在 url 开头附加 localhost://
加载本地资源并通过 WKURLSchemeHandler
处理它:
let testStr = """
<!DOCTYPE html>
<html>
<head>
<title></title>
<link href="localhost://../styles/style.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<p class="test_class1">This is a paragraph.</p>
<p class ="test_class2">This is another paragraph.</p>
</body>
</html>
"""
lazy var webView: WKWebView = {
let webConfiguration = WKWebViewConfiguration()
webConfiguration
.setURLSchemeHandler(MySchemeHandler(), forURLScheme: "localhost")
let webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.translatesAutoresizingMaskIntoConstraints = false
return webView
}()
webView.loadHTMLString(testStr, baseURL: URL(string: "localhost"))
class MySchemeHandler: NSObject, WKURLSchemeHandler {
let testCss = """
@font-face{
font-family: 'Special';
font-weight: normal;
font-style: normal;
src: url(../fonts/special.ttf);
}
.test_class1 {
font-weight: bold;
color: #007D6E;
font-family: 'Special' !important;
text-align: center !important;
}
.test_class2 {
font-weight: bold;
color: #FF7D6E;
text-align: center !important;
}
"""
func webView(_: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
print("start: \(String(describing: urlSchemeTask.request.url?.absoluteString))")
guard let url = urlSchemeTask.request.url else {
return
}
if url.absoluteString.contains("style.css") {
let data = Data(testCss.utf8)
urlSchemeTask.didReceive(URLResponse(url: url,
mimeType: "text/css",
expectedContentLength: data
.count,
textEncodingName: nil))
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
}
func webView(_: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
print("stop: \(String(describing: urlSchemeTask.request.url?.absoluteString))")
}
}
在日志中我可以看到 start
方法被本地资源触发:
start: Optional("localhost://../styles/style.css")
start: Optional("localhost://../fonts/special.ttf")
但是如果我从 <link href="localhost://../styles/style.css" type="text/css" rel="stylesheet"/>
中删除 localhost://
start
方法根本不会被触发。
据我了解自 iOS 10 WKWebview 支持 AppCache
试试这个代码
let webview: WKWebView
let config = WKWebViewConfiguration()
config.setValue(true, forKey: "_setOfflineApplicationCacheIsEnabled")
webview = WKWebView(frame: .zero, configuration: config)
WKNavigationDelegate
有一个函数来决定如何导航到一个页面,你可以在这里做过滤和重定向。像这样:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// you may need to use .linkActivated depending on how users are navigating
if navigationAction.navigationType == .other {
if let request = navigationAction.request.url {
if request.absoluteString.hasPrefix("https://somePrefix") {
// for test
// guard let localRedirect = URL.init(string: "https://google.com") else {
guard let localRedirect = URL.init(string: "localhost://someUrl") else {
decisionHandler(.allow)
return
}
decisionHandler(.cancel)
// for test
// webView.load(URLRequest.init(url: URL.init(string: "https://www.google.com")!))
webView.loadFileURL(localRedirect, allowingReadAccessTo: localRedirect.deletingLastPathComponent()) // delete last path component if you have other things that the page will load in that directory, like images or css
return
}
}
}
decisionHandler(.allow)
}
I've commented out navigating to google as a test, but left the code there so people can see easily how a redirect would work.
对调用 WKWebView.configuration
的结果设置处理程序无效,因为正在返回副本。相反,您应该创建一个 WKWebViewConfiguration
的新实例,在其上设置您的处理程序,然后将其传递给 WKWebView.init(frame:, configuration:)
:
self.schemeHandler = MySchemeHandler()
self.webview = WKWebView(frame: .zero, configuration: {
let webConfiguration = WKWebViewConfiguration()
webConfiguration.setURLSchemeHandler(self.schemeHandler,
forURLScheme: "localhost")
return webConfiguration
}())