We're trying to save the content (HTML) of WKWebView in a persistent storage (NSUserDefaults, CoreData or disk file). The user can see the same content when he re-enters the application with no internet connection. WKWebView doesn't use NSURLProtocol like UIWebView (see post here).
Although I have seen posts that "The offline application cache is not enabled in WKWebView." (Apple dev forums), I know that a solution exists.
I've learned of two possibilities, but I couldn't make them work:
1) If I open a website in Safari for Mac and select File >> Save As, it will appear the following option in the image below. For Mac apps exists [[[webView mainFrame] dataSource] webArchive], but on UIWebView or WKWebView there is no such API. But if I load a .webarchive file in Xcode on WKWebView (like the one I obtained from Mac Safari), then the content is displayed correctly (html, external images, video previews) if there is no internet connection. The .webarchive file is actually a plist (property list). I tried to use a mac framework that creates a .webarchive file, but it was incomplete.
2) I obtanined the HTML in webView:didFinishNavigation but it doesn't save external images, css, javascript
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
webView.evaluateJavaScript("document.documentElement.outerHTML.toString()",
completionHandler: { (html: AnyObject?, error: NSError?) in
print(html)
})
}
We're struggling over a week and it is a main feature for us. Any idea is really appreciated.
Thank you!
I know I'm late, but I have recently been looking for a way to store web pages for offline reading, and still could't find any reliable solution that wouldn't depend on the page itself and wouldn't use the deprecated
UIWebView
. A lot of people write that one should use the existing HTTP caching, but WebKit seems to do a lot of stuff out-of-process, making it virtually impossible to enforce complete caching (see here or here). However, this question guided me into the right direction. Tinkering with the web archive approach, I found that it's actually quite easy to write your own web archive exporter.As written in the question, web archives are just plist files, so all it takes is a crawler that extracts the required resources from the HTML page, downloads them all and stores them in a big plist file. This archive file can then later be loaded into the
WKWebView
vialoadFileURL(URL:allowingReadAccessTo:)
.I created a demo app that allows archiving from and restoring to a
WKWebView
using this approach: https://github.com/ernesto-elsaesser/OfflineWebViewThe implementation only depends on Fuzi for HTML parsing.
I would recommend investigating the feasibility of using App Cache, which is now supported in
WKWebView
as of iOS 10: https://stackoverflow.com/a/44333359/233602I'm not sure if you just want to cache the pages that have already been visited or if you have specific requests that you'd like to cache. I'm currently working on the latter. So I'll speak to that. My urls are dynamically generated from an api request. From this response I set
requestPaths
with the non-image urls and then make a request for each of the urls and cache the response. For the image urls, I used the Kingfisher library to cache the images. I've already set up my shared cacheurlCache = URLCache.shared
in my AppDelegate. And allotted the memory I need:urlCache = URLCache(memoryCapacity: <setForYourNeeds>, diskCapacity: <setForYourNeeds>, diskPath: "urlCache")
Then just callstartRequest(:_)
for each of the urls inrequestPaths
. (Can be done in the background if it's not needed right away)I'm using Alamofire for the network request with a cachingSessionManager configured with the appropriate headers. So in my WebService class I have:
Then in the webview delegate method I load the cachedResponse. I use a variable
handlingCacheRequest
to avoid an infinite loop.Of course you'll want to handle it if there is a loading error as well.
I hope this helps. The only thing I'm still trying to figure out is the image assets aren't being loaded offline. I'm thinking I'll need to make a separate request for those images and keep a reference to them locally. Just a thought but I'll update this when I have that worked out.
UPDATED with images loading offline with below code I used the Kanna library to parse my html string from my cached response, find the url embedded in the
style= background-image:
attribute of the div, used regex to get the url (which is also the key for Kingfisher cached image), fetched the cached image and then modified the css to use the image data (based on this article: https://css-tricks.com/data-uris/), and then loaded the webview with the modified html. (Phew!) It was quite the process and maybe there is an easier way.. but I had not found it. My code is updated to reflect all these changes. Good luck!Easiest way to use cache webpage is as following in Swift 4.0: -
/* Where isCacheLoad = true (Offline load data) & isCacheLoad = false (Normal load data) */