how to cache a whole web page with images in iOS - iphone

I am working on a image-text mixture page on iOS. I know it is possible for me to use UIWebView to achieve the goal. But the problem is, user may need to read the page offline.
For the text part, I can save the html to the disk and load it in the offline mode.
But how about images? Is it possible to cache the images to the disk and the UIWebView can still be able to display them?
Thank you!

The ASIHTTPRequest project has a class called ASIWebPageRequest which is designed to do exactly what you want. If you're okay with adding an additional dependency to your project then I think it's a good solution for you: ASIWebPageRequest.
On the page I liked above there are some good examples of how to use it but I'll include one of them here for completeness:
- (IBAction)loadURL:(NSURL *)url
{
// Assume request is a property of our controller
// First, we'll cancel any in-progress page load
[[self request] setDelegate:nil];
[[self request] cancel];
[self setRequest:[ASIWebPageRequest requestWithURL:url]];
[[self request] setDelegate:self];
[[self request] setDidFailSelector:#selector(webPageFetchFailed:)];
[[self request] setDidFinishSelector:#selector(webPageFetchSucceeded:)];
// Tell the request to embed external resources directly in the page
[[self request] setUrlReplacementMode:ASIReplaceExternalResourcesWithData];
// It is strongly recommended you use a download cache with ASIWebPageRequest
// When using a cache, external resources are automatically stored in the cache
// and can be pulled from the cache on subsequent page loads
[[self request] setDownloadCache:[ASIDownloadCache sharedCache]];
// Ask the download cache for a place to store the cached data
// This is the most efficient way for an ASIWebPageRequest to store a web page
[[self request] setDownloadDestinationPath:
[[ASIDownloadCache sharedCache] pathToStoreCachedResponseDataForRequest:[self request]]];
[[self request] startAsynchronous];
}
- (void)webPageFetchFailed:(ASIHTTPRequest *)theRequest
{
// Obviously you should handle the error properly...
NSLog(#"%#",[theRequest error]);
}
- (void)webPageFetchSucceeded:(ASIHTTPRequest *)theRequest
{
NSString *response = [NSString stringWithContentsOfFile:
[theRequest downloadDestinationPath] encoding:[theRequest responseEncoding] error:nil];
// Note we're setting the baseURL to the url of the page we downloaded. This is important!
[webView loadHTMLString:response baseURL:[request url]];
}

Related

How to set cache expiration time when using UIWebView and NSURLRequest?

I'm looking for the most simple default solution. Currently, I have one about view controller where some about info from some url is shown. I need to cache that for offline usage, and cache should be updated after some time, for example after a week. Currently , I'm using NSURLRequestReturnCacheDataElseLoad cache policy but don't know how to set cache expiration and cache update time:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSString *urlString = [NSString stringWithFormat:#"%#%#", kServiceBaseUrl, #"docs/about_en.html"];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:30];
[self.webView loadRequest:request];
}
I have read several posts where manual cache handling (get NSData, save, check and load) is suggested. But maybe there's more straightforward and simple solution?
You can manually clear the cache like this:
[[NSURLCache sharedURLCache] removeAllCachedResponses];
As for expiration and handling options, take a look at the NSURLRequest Class Reference dealing with cache here.
You can get a detailed explanation of Understanding Cache Access from the Apple URL Loading System Programming Guide here.

How to cache images and label texts downloaded from the internet for every uitableviewcell?

I download texts and image for every table cell from internet. I do it in background (GCD), but performance is not good (takes a while to dll images for all rows) because there are lots of rows.
I use nsurlconnection for image dll.
I googled a bit and became confused. What is the easiest way to do this?
Use NSCache (for images and text) or do i have to learn core data?
You can use AsyncImageView to download & cache images.
Checkout https://github.com/rs/SDWebImage/ for a super simple way of doing this...
From the README:
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
This is all you need for cached images. You can look at the source for how to extend this to support caching text objects too. Alternatively you could use NSUserDefaults to store Key-Value pairs based on URL and text data.
i use this lib to cashe web images
https://github.com/rs/SDWebImage
very simple to use
[imageView setImageWithURL:[NSURL URLWithString:yoururl]];
I am using blocks and ASIHTTPRequest for that and it works fine.
[ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL urlWithString:<yoururl>]];
[request setDownloadCache:[ASIDownloadCache sharedCache]];
[request setCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
[request setCacheStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
[request setCompletionBlock:^{
//do your things
}];
[request setFailedBlock:^{
//request failed - inform the user
}];
[request startAsynchronous];

How to download files to iPhone's filesystem?

i would like to download a bunch of pdf's from a website to the iPhone filesystem in my app.
Because i don't want to download the files everytime i start the app.
In the documentation i found a function called "dataWithContentsOfURL" but the sample code didn't help me. Is this the right way, or is there an easier solution?
Can someone give me a tip or two ? ;-)
greets max
I recommend using ASIHTTPRequest, it's well written, documented and easy to use, heres a quick example from one of my applications download class that downloads a JSON file using ASIHTTPRequest:
-(void)downloadJSONData:(NSString *)path destination:(NSString *)destination secure:(BOOL)secure {
if (![self queue]) {
[self setQueue:[[[NSOperationQueue alloc] init] autorelease]];
}
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:destination error:NULL];
NSURL *url = [NSURL URLWithString:path];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
if(secure){
[request addRequestHeader:#"Authorization" value:[NSString stringWithFormat:#"Basic %#",[ASIHTTPRequest base64forData:[[NSString stringWithFormat:#"%#:%#",self.username,self.password] dataUsingEncoding:NSUTF8StringEncoding]]]];
}
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[request setDownloadDestinationPath:destination];
[[self queue] addOperation:request]; //queue is an NSOperationQueue
}
I would take a look at the how-to-use page as it contains everything you need to know.
You're on the right track
-(void)downloadURL:(NSURL *)theUrl toLocalFileURL:(NSURL *)localURL
{
NSData *dlData = [NSData dataWithContentsOfURL:theURL];
[dlData writeToURL:localURL atomically:YES];
}
So just research how NSURLs are created for local and remote objects and you're all set.
Yes you could download files only as you need(Lazy load).
When ever you want access the files. Check in the documents directory for the file. In the second answer it is being specified. Append file name with the douments path. Check If there is a readable file using NSFileManager's isReadableFileAtPath:instance method. if it returns false then initiate downloading the pdf from the website.
Please do care to create a class which downloads file asynchronously. You could use NSURLconnection to initiate the request and its delegate methods to process its content After downloading content write it to documents folder.
If you could create a class for asynchronous download , you could initiate parallel downloads and use maximum use of the bandwidth.
By making asynchronous downloads you could make sure that you application is responsive even while files are getting downloaded.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/Reference/Reference.html
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html
Regards ,
Jackson Sunny Rodrigues
Do not use dataWithContentsOfURL: unless you're executing it somewhere other than the main thread. It is a blocking API as well as one that accesses the network. That is a recipe for bad user experience.
Think about this: You use a blocking API to access the network, but the network is down or really slow. The main thread is now blocked, so your user interface is not responding to user interaction. The user gets frustrated and tries to cancel the download using the handy button you put on the UI, but "OH NO!" it doesn't work because the UI is blocked.
Do not use blocking APIs on the main thread.
You should look at the documentation for NSURLConnection and it's asynchronous loading methods for downloading data.
You should use
NSData* pdf_data = [NSData dataWithContentsOfFile: #"file_path"];
where file_path is the path where you've saved your file (it is usually Documents or Library directories).
To save data there use:
[pdf_data writeToFile: #"file_path" atomically: YES];
To get path to your documents directory you can use:
- (NSString *)applicationDocumentsDirectory {
NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}

saving image in UITableViewCell

I am loading an image from an API in my tableviewcell, it downloads the image everytime I scroll down the UITableView. So what is the best way to save this image and so it doesn't have to download the image again if it is already there?
Same thing for text, how can I save the text...
If it's not very big, you can download it once and save it into user preferences (NSUserDefaults) as NSData object. Works for me.
Alternatively, you can use asynchronous requests with NSUrlConnection and implement caching in any way you like. (For example, update image only once a week.)
Moreover, even default cache settings of NSUrlConnection might work good enough.
More on caching
edit
An example of asynchronous request.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: url]];
URLConnectionDelegate *delegate = ...;
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:delegate];
if (!connection) {
// something went wrong
}
In delegate, you mainly need methods to handle received data and to finish connection.
Assume you have NSMutableData *receivedData object.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// success, do whatever you want with data
[receivedData release];
[connection release];
}
The link above and API documentation provide more details about delegate structure.
Look at the LazyTableImages sample application in the iOS site sample code section. It has a great example of how to download images for a table cell using NSUrlConnection asynchronous calls and storing the images (and text) in an NSMutableArray.
This sample demonstrates a multi-stage
approach to loading and displaying a
UITableView. It begins by loading the
relevant text from an RSS feed so the
table can load as quickly as possible,
and then downloads the images for each
row asynchronously so the UI is more
responsive.

iPhone SDK: UIWebView to stop images from loading/downloading

How can I use the UIWebView in Xcode so that when it loads up pages it DOESN'T download the images (to cause a faster loading page)?
UIWebView is a pale, poor little shadow of WebKit's full WebView, for which this is easy. -webView:shouldStartLoadWithRequest:navigationType: only gets called for navigation. It doesn't get called for every request like WebPolicyDelegate does on mac. With UIWebView, here's how I would attack this problem:
Implement -webView:shouldStartLoadWithRequest:navigationType: and set it to always return NO. But you'll also take the request and spawn an NSURLConnection. When the NSURLConnection finishes fetching the data, you're going to look through it for any IMG tags and modify them to whatever placeholder you want. Then you will load the resulting string into the UIWebView using -loadHTMLString:baseURL:.
Of course parsing the HTML is not a trivial task on iPhone, and Javascript loaders are going to give you trouble, so this isn't a perfect answer, but it's the best I know of.
expanding on Rob's answer.
I noticed that when loadHTMLString:baseURL: and always returning NO, that webView:shouldStartLoadWithRequest:navigationType: just keeps getting called. (i suspect loadHTMLString invokes another shouldStartLoadWithRequest).
so what I had to do was alternate between returning YES/NO
and I used NSScanner to parse the HTML and change src="http://..." to src=""
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (pageHasNoImages==YES)
{
pageHasNoImages=FALSE;
return YES;
}
NSString* newHtml;
NSString* oldHtml;
NSData *urlData;
NSURLResponse *response;
NSError *error;
urlData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
oldHtml=[[NSString alloc] initWithData:urlData encoding:NSUTF8StringEncoding];
newHtml=[self.detail scannerReplaceImg:oldHtml]; // my own function to parse HTML
newHtml=[self.detail scannerReplaceAds:newHtml]; // my own function to parse HTML
if (newHtml==nil)
{
NSLog(#"newHtml is nil");
newHtml=oldHtml;
}
[oldHtml release];
pageHasNoImages=TRUE;
[web loadHTMLString:newHtml baseURL:request.URL];
return NO;
}
Be the delegate for the UIWebView, then intercept the call:
– webView:shouldStartLoadWithRequest:navigationType:
Check the values of navigationType in the documentation. I believe you'll be best served by returning NO on navigationType == UIWebViewNavigationTypeOther.
does this actually cause the page to load faster?
it sounds like the images are still being downloaded, but we're just not feeding them to the UIWebView.
or does shouldStartLoadWithRequest just load the HTML text first?