UIWebView and NSURLCache have a troubled relationship - iphone

I am trying to solve 2 things in my UIWebView:
Load local assets in place of remote ones, under the right conditions
Load remote assets as normal if there are no local ones or they are outdated
Allow the webview to go "back" without needing to reload it's html form the server
So to handle #1 and #2, I implemented a custom subclass NSURLCache. It gets called when the webview requests assets, and I can send along the local ones instead, aborting the server call for the asset. I even have a little timestamping system that knows if the local version is old or not to figure out which one to use.
But #3 is throwing me for a loop. It seems that regardless of cache-control headers, UIWebView will ALWAYS reload the HTML when going back.
So I tried to force caching of the HTML in my custom NSURLCache class. It seems to work, and returns the instance of NSCachedURLResponse with the HTML I want, but the UIWebView fails to notice and loads it from the server anyway. After some searching I see there is some "inconsistencies" with UIWebView and the url cache, and I guess this is one. Works great for assets, but not so great for the HTML that makes up the page it's trying to display.
So then I decided that since this may not work, do it manually. I created a stack of urls, and their data that I fetch manually and load it with loadData:MIMEType:textEncodingName:baseURL:. Lo and behold it seemed to work great! I could now go back, the web view delegate would intercept the load, and tell it to load my manually fetched data instead, and the server would not be hit.
However, then I noticed that the NSURLCache was no longer being used for my assets. It seems it only asks for cached assets if you load content with loadRequest: and not by loading data or an html string with the baseUrl set properly.
So despite all my efforts I cannot make a web view conditionally load local assets AND navigate back without hitting the server. It seems like I have a few bugs here in the SDK.
UIWebView ignores the servers Cache-Control header for HTML content
UIWebView asks for a NSCachedURLResponse's for the main HTML request but is then ignored and discarded.
UIWebView asks the NSURLCache for assets if you load it up with a request, but when loading with HTML or data it bypasses the cache completely and requests it directly from the remote server.
So first up, anyone see a solution to these things? And second, any reason that I am being stupid and these are not actually SDK bugs?

To tackle your first #3:
While it may seem a little unorthodox, how about persisting the downloaded HTML to an NSUserDefaults object as a flat string, and restoring it (conditionally) later when you need it?

I've abandoned this. There were just too many gotchas. Instead I just load new page in via ajax. And instead of [webview goBack] I have some custom javascript to do cool stuff for me on [webview stringByEvaluatingJavascriptFromString:#"goBack()"]

Related

Getting images by parsing constantly changing HTML

I'm in the process of developing an iOS app that retrieves images from a URL (http://m.9gag.com). It so happens that the HTML for the URL is in constant change and whenever I have a working code, the site's style changes.
Is there any way I could pull those images from the HTML without having to worry about webpage changes? There is no public API at the moment so that's sadly not an option.
I look forward to hearing some options.
Also, if the page is set so that when the user scrolls to the bottom, it loads more content, how can I get more html to load based on how far down in the HTML parsing I've got? I'm not using a webview, I just need to update the HTML I initially retrieved.
It seems that the simplest way in your case - use regular expression (for example http://[A-Za-z0-9_/\.]*\.jpg) to extract URLs and keep track of already pulled images.

How can I save a html file with external resources using AFNetworking?

I would like to save a .html webpage with AFNetworking, but would also like to save the resources (such as .css files, .js files, images etc) within the webpage so that the whole webpage can be viewed offline.
Is this possible with AFNetworking, and how would I do it? Could a short example be posted please?
Thanks!
AFNetworking is not necessary to do this. Instead, what you want to do is use an NSURLCache subclass that supports disk cacheing (such as Peter Steinberger's fork of SDURLCache). With that in place, just load up a URL using a UIWebView (this may not necessarily have to be displayed to a user), and subsequent loads should use that local cache.
At the very least, do not waste your time trying to write something on your own to download assets on a webpage. This process requires a web browser (which UIWebView qualifies as) to determine everything needed to load.

Unable to see any local or remote images in UIWebView when loadHTMLString is used to load page

No matter what I try, the only way I can see any images in my UIWebView, is if I load a page using loadRequest. loadHTMLString will load everything except for images.
I have tried using all the online examples of setting the base url. Nothing seems to work. I've tried setting the base url to my bundle path, no local images display. I've tried setting the base url to http://www.google.com and pasting the google home page source into a test file, load it with loadHTMLString. The page shows up with no images.
What am I doing wrong?
I'd recommend taking a peek at this...
UIWebView links
Also you may notice that between the simulator and the real device files become case sensitive on the device... I'm not sure if UIWebView makes it case insensitive but thought it might be worth mentioning incase it gives any more food for thought.
If this doesn't work perhaps you can post your code snippet as this may help.

UIWebView - remote html loading local images

I'm trying to develop an application using UIWebView. The app is loading remote URL's, so in order to make it quick I want it to use the images included in the application bundle. I was wondering if there is a way to do it without forcing the application to manually replace the address of each image to point into local resources?
I doubt it, unfortunately. Browser security models in general (and this applies to browsers on the desktop as well of course) don't allow file:// scheme URLs to be used in the context of any other scheme (http://) page. If this was allowed, then arbitrary sites you browse to could load local files and possible access private user data, which would be Bad. Local images with the file:// scheme will only work in a page that is itself in the file:// domain.
I'm not sure how to tell you to accomplish this, but perhaps someone else has a clever idea.
You can use this:
Stop Images from loading in UIWebView
...which sounds horrendous, but is actually quite easy to do, and lets you replace requests for the remote URL with a hit to the local URL.
You'll have to convert the URLs manually, of course.

iPhone application cache and XMLHttpRequest

I have a WebApp that I've been try to make work offline. The WebApp is too big, even minified, to simply use the application cache (things download but I eventually get a window.applicationCache error). I'm trying to use XMLHttpRequest to get the larger scripts and main html and keep them in localStorage and just keep a small loader script in the application cache. The problem I'm seeing is that the XMLHttpRequest returns a network error when the loader script is being served locally. When the the cache is downloading no error is returned and it works fine. When I turn off the application cache the loader works fine, but of course then I need the network to get the loader.
I tried setRequestHeader("Cache-Control", "no-cache") but that didn't help.
Anybody have a clue?
What does your network: section in your manifest look like?
I found that if I weren't allowing wildcard network traffic it wouldn't load with XMLHttpRequest. So changing it to:
Network:
*
did the trick for us.
I think I found a solution. It would probably work for others.
I split the loader into two separate HTML files: one that uses XMLHttpRequest to get all the required files and put them in localStorage (the loader) and another that simply reads the files from localStorage and writes them into the document (the booter) with appropriate wrappers (e.g. ). The booter has a manifest file to keep it in the application cache. The loader does not. The user first invokes the booter. If the booter finds files already in localStorage it does it's thing. Otherwise, it uses location.replace() to invoke the loader. The loader loads the files from the server using XMLHttpRequest and puts them in localStorage, and then re-invokes the booter using location.replace(). This seems to not cause an network error.
In order to run offline, the user must invoke the booter in the iPhone Safari browser (which invokes the loader, which re-ivokes the booter) which boots the WebApp. In Safari, the user must then add the WebApp (the booter link) to their Home Screen (using the "+" button at the bottom). When offline the user can get to the app from the Home Screen icon. It takes a few seconds to re-render, but it's fully functional after that. It's the same delay when online. Invoking the link from the iPhone Safari browser will not work offline, though it will work online.
The booter monitors the application cache's "updateready" event so that when online and the when iPhone detects a change in the booter's manifest file and downloads a new booter, it will swap the new cache (window.applicationCache.swapCache()) and invoke the loader using location.replace() again. I also add an alert() to let the user know something funky is going on. So changing the manifest file (I mean making some bytes different, not just tweaking the modify time) will cause clients to get new files when online.
Interestingly, I noticed that localStorage set up in Safari is not available to the same page served from invoking the Home Screen icon, even though the cookies transfer! So the first time the booter is invoked from the icon it will reload the files even though they were previously loaded in Safari. Also, I had to explicitly prevent the loader from being cached as it was not reloading from the server when the rest of the files were updated.
You are correct. Ultimately it was the network section in the manifest.
I thought the site where the application was loaded from was included automatically and you didn't need to mess with it, but it's not true. You need to put the site in the network section.