Caching web page in IOS using NSURLCache subclass - ios

I subclassed NSURLCache and overriden – cachedResponseForRequest: and – storeCachedResponse:forRequest: . I am storing all the files am getting in the application's documents directory.
Now, the problem is, the .html file when opened doesn't load all the resources (i.e, images,js and css). I didn't know wat the problem was, hence I did a "save page as" on the same web page and compared both the .html files (the one i saved from the browser and the one i cached.).
I found that in the html file saved from the browser, all the images and other files source paths have been replaced with the local paths..
<div><img src="./CNN.com International - Breaking, World, Business, Sports, Entertainment and Video News_files/footer_cnn_logo.png" width="23" height="11" alt="" border="0" class="cnn_ie6png">
But the html file that i cached, still has the url of the website..
<div><img src="http://i.cdn.turner.com/cnn/.e/img/3.0/global/footer/pngs/footer_cnn_logo.png" width="23" height="11" alt="" border="0" class="cnn_ie6png"/>
So how do I change it..?or what's the right way to save a web page along with all the resources..??
AM saving all the files am getting in storeCachedResponse:forRequest: method as it is. So do I have to do some modifications to this .html file before saving, so that it would refer all the resource path in the local folder properly ??
FYI, I do not want to use ASIWebPageRequest (Download and cache entire web page on ios)
//////////// Edit ////////////
So as Edwin Vermeer has answered, am able to get the right local file from my cache and return the response. but looks like, the UIWebView doesn't understand that data..!
This is how am returning the response in cachedResponseForRequest: method
if (shouldFetchFromCache) {
NSData* content = [NSData dataWithContentsOfFile:storagePath];
NSDictionary *headers = #{#"Access-Control-Allow-Origin" : #"*", #"Access-Control-Allow-Headers" : #"Content-Type"};
NSHTTPURLResponse *urlresponse = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:200 HTTPVersion:#"1.1" headerFields:headers];
cachedresponse = [[NSCachedURLResponse alloc] initWithResponse:urlresponse data:content];
}
return cachedresponse;!
This is how the web page loads on the webView..
Screenshot of the app
Is there some different way to return the response...? by the way, neither content or the response are nil.
an example of the request url is
Request : http://z.cdn.turner.com/cnn/tmpl_asset/static/intl_homepage/1021/js/intlhplib-min.js
and the file path where its stored is :
file path: /Users/akshay/Library/Application Support/iPhone Simulator/5.1/Applications/86ED671C-AE45-449A-A2E1-D08281AB54CF/Documents/CVWebCache/cnn/tmpl_asset/static/intl_homepage/1021/js/intlhplib-min.js

If your custom NSUrlCache is still active, then the requests for the resources should go past it. You could there test for the URL and return the correct file from your documents folder.
You could then also open the original URL instead of the file from the documents folder. Again your NSUrlCache will return the file from your document folder.
You do not have to create a NSHTTPUrlResponse. A NSURLResponse is good enough.
if (shouldFetchFromCache) {
NSData* content = [NSData dataWithContentsOfFile:storagePath];
NSURLResponse* response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:#"cache" expectedContentLength:[content length] textEncodingName:nil] ;
cachedresponse = [[NSCachedURLResponse alloc] initWithResponse:response data:content];
}
}
return cachedresponse;

Related

Loading local images into WKWebView

I'm trying to get WKWebView to display locally downloaded images in a WKWebView. The webview normally displays HTML, which is retrieved remotely. The contents of the HTML can sometimes contain remote links to images. My app parses the HTML and looks for these HTML tags, downloads the file it is referencing and subsequently replaces the remote link with a local one.
Normally speaking, this wouldn't be very difficult but the images aren't being displayed, presumably due to the images and the local HTML files for the webview being in two separate directories (the documents directory and the app bundle directory respectively).
I've seen people suggest moving the download destination of the images to the same directory as where the HTML files are but this isn't an option for me as I don't want to start mixing up files downloaded by the user with local assets.
What would be my best course of action here?
Well, I've found a workaround. Instead of locally storing the images and referencing them in the HTML files, I'm now instead converting the images to Base64 and then adding them to the HTML. It's not ideal but it gets the job done. I'm going to leave this question open in case someone ever manages to find an actual solution.
To display cached HTML referencing cached resources in a WKWebView:
For each of the resources within your HTML content string, cache it into the directory as provided by NSTemporaryDirectory(). So an image tag like:
...<img src='https://www.myimage.com/example_image.png'/>...
should be cached and replaced into something like this:
...<img src='/private/var/mobile/Containers/Data/Application/527CF4FC-9319-4DFF-AB55-9E276890F5DC/tmp/example_image.png'/>...
Now cache the HTML content string with the replaced resource URLs. It must also be cached in the directory provided by NSTemporaryDirectory(). One difference here is that it must be cached (and later referenced) using the file:// protocol as a restriction of caching the string using NSData (see sample code).
For example file:///private/var/mobile/Containers/Data/Application/527CF4FC-9319-4DFF-AB55-9E276890F5DC/tmp/my_html_content_string.html
A few things to point out:
You cannot load the HTML as a raw string (loadHTMLString:baseURL:).
You cannot reference the cached resource within your HTML string using the file:// protocol. That may work in a UIWebView, but will not work in the WKWebView.
Objective-C
// To cache the HTML string:
NSString *HTML = <HTML CONTENT WITH CACHED RESOURCES>;
NSData *data = [HTML dataUsingEncoding: NSUTF8StringEncoding];
[data writeToURL: cachedHTMLURL atomically: YES];
// To load the store HTML file:
[myWKWebView loadRequest: [NSURLRequest requestWithURL: cachedHTMLURL]]; // (file://.../tmp/my_html_content_string.html)
Swift
// To cache the HTML string:
let HTML = <HTML CONTENT WITH CACHED RESOURCES>
let data = HTML.data(using: String.Encoding.utf8)
do {
try data.write(to: cachedHTMLURL, options: .atomic)
} catch {
print(error)
}
// To load the store HTML file:
myWKWebView.load(URLRequest(url: cachedHTMLURL)) // (file://.../tmp/my_html_content_string.html)
I had the same problem with WKWebView as it can not load both html strings and images at the same time for security purposes. I switched to UIWebView, which is deprecated, but I was able to load both html strings and referenced images at the same time.
I developed a definitive solution for the company I work for. But it relies on the html / javascript side. Anywhere inside your html code where you will reference to a local image <img src="..."/> you should set this "src" dynamically, and it will work seamlessly.
function getLocalURL(path) {
let origin = window.location.origin
if (origin == "file://") {
return origin + window.location.pathname.replace("/index.html","") + path
}
return path
}
You should, clearly, rename index.html to whatever is your main .htm(l) filename :)
Usage:
getLocalURL("/local_images/location_icon.png")
Will return a WKWebView working path for the referenced local image path:
"file:///Users/arthurdapaz/Library/Developer/CoreSimulator/Devices/5073AF19-26A0-460E-BC82-E89100B8E1AB/data/Containers/Data/Application/2B099343-0BF5-4849-B1C2-2512377A9772/Documents/distDriver/local_images/location_icon.png"

iOS UIWebview does not load new css

In my app,I download an HTML app and save it in documents directory.I open it using a UIwebview. This works fine. But the problem is,if I make any css change on my server and then download the app again,the changes are reflected in documents directory but when I open the app in UIwebview , the changes are not reflected there. If I open the .html file in safari , the css changes work.
I load my html file using the following code:
appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20.0];
[self.webView loadRequest:appReq];
where appURL = [NSURL fileURLWithPath:"path to downloaded html app in documents directory"];
I tried to clear the caches using following methods:
[[NSURLCache sharedURLCache] removeCachedResponseForRequest:appReq];
and
[[NSURLCache sharedURLCache] removeAllCachedResponses];
but they do not help.
P.S : The changes are only reflected if I reopen my application.
Does anyone has any clue how to solve this?
You should generate a rendom number and append it to your path (to ignore the cache).
Instead of:
appURL = [NSURL fileURLWithPath:"path to downloaded html app in documents directory"];
use:
int randomNumber = rand() % 1012074;
appURL = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#&ignoreCache=%d",<path to downloaded html app in documents directory>,(int)randomNumber]];
I hope it will help you!
Good luck!

ios share extension - files from dropbox not loading correctly

When I try to load a shared item, the data that comes back is dropbox's login page - as if I weren't authenticated.
Here is the current method I am using to get the file data:
[itemProvider loadItemForTypeIdentifier:docType options:nil completionHandler:^(NSURL *url, NSError *error) {
//my code
}];
doctype is an appropriate kUTType like kUTTypeImage or kUTTypeText, for example. The mimeType that we write the file with is correct to, per other files. It's the actual content loaded from dropbox (just a login page every time).
I have used other variations of the method (UIImage *, and NSData *) but get the same result for dropbox files.
Our shared extension works fine with files that are downloaded in apps like goodreader or Files. The problem arises when I try to share a file from the dropbox app. It gives me a url that I can put into any browser and it will take me to the file, so the url is not the problem.
Has anyone else faced this?
Here is an example link to a document that does this:
https://www.dropbox.com/s/qxkd1957qf7iq9x/04%20-%20Test%20Document.doc?dl=0
Thank you for your help on this Greg. I found that this worked instead by just changing the url and setting dl=1 like so
From:
https://www.dropbox.com/s/qxkd1957qf7iq9x/04%20-%20Test%20Document.doc?dl=0
To https://www.dropbox.com/s/qxkd1957qf7iq9x/04%20-%20Test%20Document.doc?dl=1

Clear UIWebView cache when use local image file

I use a UIWebView to load a local html, and there is a PNG file inside the html created by Objc.
After the PNG file has been modified, I reload the html in UIWebView, but the image doesn't change. However, if I quit the app and reopen it, the image file will be changed to the new one.
I have checked the PNG file in Documents with Finder, so I'm sure it has been modified, but the old one is still in UIWebView.
So, as I think that it's a UIWebView cache problem, I've tried:
[[NSURLCache sharedURLCache] removeAllCachedResponses];
[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:url isDirectory:NO ] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:1]]; or NSURLRequestReloadIgnoringCacheData
None of them works, and I can't change the filename, because the PNG file is used in a lot of places (other html and objc code).
I've tried this too:
some.png?r=randomNumber
but it can't be showed.
How do I clear the UIWebView cache when using a local image file inside a local html?
Other than renaming every file on each access, I've only seen one thing work for this and that is modifying the HTML with javascript to add a timestamp onto the image url so it tricks the webview into thinking it's a different file. Images (usually) load the same no matter what you put after the ? in their url. I think this would be easier than renaming every file each time you load the web view. Something like this (using jQuery):
<img src="myimg.jpg" />
<script>
$(function() {
$('img').each(function(i, el) {
$(el).attr('src', $(el).attr('src')+'?pizza='+(new Date()).getTime());
});
});
</script>
I guess this is assuming that this javascript loads and runs before the images are loaded, but in theory this should work.
For a little background, I've made a page in a webview that used RequireJS to asynchronously load quite a few files. I had the EXACT same problem that this question is talking about except that I was loading javascript files instead of images. The key to fixing this issue was adding a timestamp to every path of javascript file and thus tricking the webview (ex me.js?ts=236136236 and whatever.js?ts=3153524623). I found this to work great.
One other thing I needed to do was add a timestamp to the path of my HTML file when loading it into the webview like this:
[NSURL URLWithString:[[NSString stringWithFormat:#"%#/index.html?pizza=%f", webDirectoryPath, [[NSDate date] timeIntervalSince1970]]
I now can modify all the local files and each time the webview appears the changes come through.
You can try this, in your AppDelegate.m
+(void)initialize {
[[NSURLCache sharedURLCache] setDiskCapacity:0];
[[NSURLCache sharedURLCache] setMemoryCapacity:0];
}
If your html didn't change and the only change was in image you should use UIWebView's reload message instead of loading request again.
Something like this:
- (void)reloadWebView:(id)sender
{
if (self.loaded) {
[self.webView reload];
} else {
NSString *html = [[NSBundle mainBundle] pathForResource:#"web" ofType:#"html"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:html]];
[self.webView loadRequest:request];
self.loaded = YES;
}
}
You don't even need any manipulations with cache.

NSURLCache and .svg files

I am caching locally some of the larger files required for a UIWebView and have a subclass of NSURLCache with a custom implementation to help serve these files.
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
I am using this to hijack the requests and return a locally stored copy of the files (mainly t
The body of cachedResponseForRequest:request (without the boiler plate) is essentially:
// logic to figure out what the local file is, load it into a NSData object (f)
NSURLResponse *r = [[NSHTTPURLResponse alloc] initWithURL:request.URL MIMEType:mimetype expectedContentLength:[f length] textEncodingName:nil];
NSCachedURLResponse *cr = [[NSCachedURLResponse alloc] initWithResponse:r data:f] ;
[super storeCachedResponse:cr forRequest:request];
return cr;
This works correctly for all of the cached content apart from a single svg image. When the svg image is attempted to load it will proceed through cachedResponseForRequest:request, build a NSCachedURLResponse object and return it.
However the next thing the application does is download the file from the remote server and then any subsequent request are served from the cache. We dont want to download the file remotely as its relatively large and impacts performance.
Does the NSURLCache deal with .svg files differently to other filetypes?
The maximum size of files that will be handled by the NSUrlCache can be influenced by the parameters when you initialize the cache with:
initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)diskPath
The maximum file size is related to the memoryCapacity. You should set that to at least 10 (why?) times the maximum file size that you want to handle.
I have tried this with files up to 15MB using https://github.com/evermeer/EVURLCache

Resources