Im using this tutorial to load an image asynchronously in my app. I modified that code so the picture saves to the iPhone's local files and can be loaded while offline. I want to make it so this load request times out after a certain interval, possibly 15-20 seconds, and loads the saved file instead of downloading a new one. I found ways to make a web view time out, but Im not sure how to go about doing this using the asynchronous method. How can I make a timeout request for the way that this code loads the url?
Thanks
Edit: I want to make it time out if it is unable to connect to the website and also if the downloading of the picture takes too long.
- (void)viewDidLoad
{
[super viewDidLoad];
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(loadImage)
object:nil];
[queue addOperation:operation];
}
- (void)loadImage {
NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://www.TestURL.com/test.jpg"]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localFilePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"test.jpg"]];
[imageData writeToFile:localFilePath atomically:YES];
[self performSelectorOnMainThread:#selector(displayImage:) withObject:image waitUntilDone:YES];
}
if you use NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://www.TestURL.jpg"]]; You are creating a synchronous connection, so You canĀ“t cancel it. You need to wait till the end.
You should implement an asynchronous download using NSURLConnection as explained in How can I deal with connection problem when I use NSData?
Now, I am a newbie at this to, and I realize that this is a way to do it but really doesn't answer the question, but how I handled this situation was to write a method that first checked locally for the image, and if it wasn't there, load it from the web and save it locally, so it was there the next time. Here is some code.
- (UIImage *)checkForLocalImageThenSave:(NSString *)name fromStringURL:(NSString *)url {
NSLog(#"********** Start loading image **********\n\n");
UIImage *image = [[UIImage alloc] init];
NSString *localDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *profilePicName = [NSString stringWithFormat:#"/%#", name];
NSString *profilePicNameOnline = [NSString stringWithFormat:#"%#", url];
NSString *directoryWithProfilePicName = [localDirectory stringByAppendingString:profilePicName];
NSLog(#"Looking for file: %#", directoryWithProfilePicName);
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:directoryWithProfilePicName];
if (fileExists) {
NSLog(#"File exists. Load: %#\n\n", directoryWithProfilePicName);
image = [[UIImage alloc] initWithContentsOfFile: directoryWithProfilePicName];
NSLog(#"********** Loading image done **********\n\n");
} else {
NSLog(#"File does not exist. Save: %#", directoryWithProfilePicName);
// TO SAVE A JPEG FILE
NSData *imageWithURL = [[NSData alloc] initWithContentsOfURL:[[NSURL alloc] initWithString:profilePicNameOnline]];
NSLog(#"File at? %#", profilePicNameOnline);
image = [[UIImage alloc] initWithData:imageWithURL];
NSString *jpegFilePath = directoryWithProfilePicName;
NSData *data = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];//1.0f = 100% quality
[data writeToFile:jpegFilePath atomically:YES];
NSLog(#"Saving image done.");
}
return image;
}
Related
I am using the following code to download images. Can someone confirm the images are being downloaded asynchronously as they would appear to be? Normally, they download rapidly but every now and then the UI freezes for a minute while data comes through so something would appear to be awry:
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) //1
NSString *picURL = [NSString stringWithFormat:#"http://~/pics/%#",picname];
NSURL *urlPicUrl = [NSURL URLWithString:picURL];
dispatch_async(kBgQueue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:picURL]];
if (imgData) {
UIImage *imageCache = [[UIImage alloc] init];
imageCache = [UIImage imageWithData:imgData];
if (imageCache) {
[self saveImage:imageCache asPicName:picname];
dispatch_async(dispatch_get_main_queue(), ^{
});
}
}
});
EDIT:
Here is code to save image.
- (void)saveImage: (UIImage*)image asPicName: (NSString*)picname
{
if (image != nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithString: picname] ];
NSData* data = UIImagePNGRepresentation(image);
[data writeToFile:path atomically:YES];
}
}
we are currently developing an enterprise app for our company.The app is supposed to work offline, This means we need to download more than 10K product images to the iPad.Each image can be from several KB to 4M.For doing so at first we used the following code to download images:
-(BOOL)DownloadFileInBatch :(NSArray*) fileArr
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"taskDone" object:self];
}];
}];
#autoreleasepool
{
NSBlockOperation *operation;
for(int i = 0; i < fileArr.count; i++)
{
operation = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [[NSURL alloc] initWithString:[[fileArr objectAtIndex:i] objectForKey:#"image_url"]];
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *filename = [documentDirectory stringByAppendingString:[NSString stringWithFormat:#"/%#", [url lastPathComponent]]];
NSLog(#"downloading file : %# : no : %d", [documentDirectory stringByAppendingString:[NSString stringWithFormat:#"/%#", [url lastPathComponent]]], i);
BOOL success = [data writeToFile:filename atomically:YES];
if(!success)
{
// failed to download
}
}];
[completionOperation addDependency:operation];
}
}
[queue addOperations:completionOperation.dependencies waitUntilFinished:NO];
[queue addOperation:completionOperation];
return YES;
}
But when we are using this code to download, the app crash due to memory problem.(even putting #autoreleasepool did not solve the crashing problem). It seems downloading one-by-one (or even 4 images parallel)is not a suitable option for this amount of data since it cause us memory problem.I just want to know what is the best way to download this amount of images from server to the iPad?
Thanks in advance...
I'm downloading an image and then displaying it with UIImageView. Using this approach the image is downloaded every time the view is loaded. How would I go about storing it locally to avoid an unnecessary server request?
[self requestData ] ;
UIImage *userImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:userProfileImageUrl ]]] ;
[self.profileImageView setImage:userImage ] ;
I would suggest using a library like SDWebImage that handles caching and asynchronous download.
Very first time, you have to save the image into NSDocumentsDirectory. For that you have to take the path of directory and append imageName like this
NSString * documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString * documentsPathAppend = [documentsPath stringByAppendingFormat:#"/%#",[userProfileImageUrl lastPathComponent]];
And you have to write image with folowing condition
if(![[NSFileManager defaultManager]fileExistsAtPath:documentsPathAppend])
{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:userProfileImageUrl]]];
[data writeToFile:documentsPathAppend atomically:YES];
}
After that you have to add this path into your local storage like core data. From next time you have to check whether image is there or not for particular URL in your core data.If it is there, then fetch the path from your core data and show like this
[self.profileImageView setImage:[UIImage imageWithContentsOfFile:imageEntity.imagePath]];
try this method to store UIImage in Local Directory..
- (void)addImage:(UIImage *)image toCacheWithIdentifier:(NSString *)identifier {
NSString *folderPath = #"LOCAL DIRECTORY PATH ";
if(![[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:nil])
{
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *fileName = [NSString stringWithFormat:#"%#.png",identifier];
fileName = [folderPath stringByAppendingPathComponent:fileName];
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:fileName atomically:YES];
}
to retrieve image from Local Directory try this method...
- (UIImage *)imageFromCacheWithIdentifier:(NSString *)identifier
{
NSString *folderPath = #"LOCAL DIRECTORY PATH ";
NSString *fileName = [NSString stringWithFormat:#"%#.png",identifier];
fileName = [folderPath stringByAppendingPathComponent:fileName];
if([UIImage imageWithContentsOfFile:fileName])
{
return [UIImage imageWithContentsOfFile:fileName];
}
return nil;
}
One other viable approach is to utilize NSURLCache (see also: Understanding Cache Access) in the official documentation.
The HTTP Cache was invented exactly for this requirement: Caching in HTTP in RFC 2616.
Unfortunately, HTTP Caching is an advanced topic, and the exact behavior of NSURLCache is quite obscure.
i have saved image in document directory with following code and after it image is there in documentary directory successfully
UIImage *image =[[UIImage alloc ]init ];
image = [UIImage imageNamed:#"PlaceHolder.png"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithString: #"PlaceHolder.png"] ];
NSData* data = UIImagePNGRepresentation(image);
[data writeToFile:path atomically:YES];
now i want to display image in table view
as in my table view some images are getting displayed from facebook url thats why i am using following code
NSString *path = [[_arrayForTable valueForKey:#"URL"] objectAtIndex:[indexPath row]];
NSURL *url = [NSURL URLWithString:path];
AsyncImageView *imageView = [[[AsyncImageView alloc] init] autorelease];
imageView.frame = CGRectMake(0, -5, 45, 45);
imageView.imageURL=url;
_arrayForTable is array of Dic and it includes URL some links are from facebook and some are from document directory
now my problem is as follows
** Only facebook images are getting displayed in table view but document directory images are not getting displayed**
i have checked location are perfectly correct for image but now displaying
NSURL *url = [NSURL URLWithString:path];
This is wrong in your code. Replace it with this line of code.
NSURL *url = [NSURL fileURLWithPath:path];
Check if file is exists in your documents directory or not
if([[NSFileManager defaultManager] fileExistsAtPath:path])
NSURL *url = [NSURL fileURLWithPath:path];
Also check in your documents directory if the image is getting saved or not. You are given the name of the image like this
image = [UIImage imageNamed:#"PlaceHolder.png"];
so if you are repeating this line your images will be overwritten in documents directory.
You can also put a check here
BOOL success = [data writeToFile:path atomically:YES];
if(success)
NSLog(#"image written successfully");
else
NSLog(#"image writing failed");
Hope I helped.
In your .m file
#interface YourClass ()<NSURLConnectionDelegate>
#property (nonatomic, copy) NSMutableData *receivedImageData;
#end
#implementation YourClass
- (void)init {
if (self = [super init]) {
// Do your init stuff
_receivedImageData = [[NSMutableData alloc] initWithCapacity:0];
}
}
// A method to start your connection
- (void)startDownloadingImage {
NSMutableURLRequest *downloadImageRequest = [[NSMutableURLRequest alloc] init];
downloadImageRequest.URL = [NSURL URLWithString:#"http://www.theImage.you/want/to/download.extension"];
downloadImageRequest.HTTPMethod = #"GET";
[NSURLConnection connectionWithRequest:downloadImageRequest delegate:self];
}
// You need the following delegate methods
#pragma mark -
#pragma mark NSURLConnection delegate methods
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse {
return request;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// This method is called frequently ... you can see if you log
// NSLog(#"I am downloading");
[self.receivedImageData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// Log an error or do whatever you want ;)
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Create your picture here using
UIImage *image = [UIImage imageWithData:self.receivedImageData scale:1.0];
// Only if there is an image display the downloaded image
if (CGSizeEqualToSize(self.imageView.image.size, CGSizeZero)) {
self.imageView.image = image;
}
}
You could try to download the image, if there is no image you can keep displaying the PlaceHolder.png
Make sure you copied the image and it really is in your project directory
// self.imageView is an UIImageView
self.imageView.image = [UIImage imageNamed:#"PlaceHolder.png"];
[self startDownloadingImage];
Let me know if it helped you ;)
Did you check the image data is not nil for the Document directory image?you have to retrieve the image by correct path and make sure that you are giving correct positions
AsyncImageView is a subclass of UIImageView. You can simply set the image property after you load the image using initWithContentsOfFile:filePath. You just have to figure out which images you want to load from the directory, and which ones from the web.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [[_arrayForTable valueForKey:#"URL"] objectAtIndex:[indexPath row]];
NSURL *url = [NSURL URLWithString:path];
if ([path rangeOfString:documentsDirectory].location != NSNotFound)
url = [NSURL fileURLWithPath:path isDirectory:NO];
AsyncImageView *imageView = [[[AsyncImageView alloc] init] autorelease];
imageView.frame = CGRectMake(0, -5, 45, 45);
imageView.imageURL=url;
Does Asynchimageview support loading image from document directory or the bundle with image url??..Try to load an image from the bundle first. Recently i had a similar problem and ended up loading a placeholder from server itself.
Like in some other answers you can first create a UIImage object with initWithContentsOfFile: set asynchimageView.image.
I want to retrieve a folder containing 10 images from server, then store that folder in my document directory. I did some code, but when I run it, I am getting the image urls, not the images themselves. Can anyone help me out?
My code:
-(void)viewWillAppear:(BOOL)animated
{
NSMutableData *receivingData = [[NSMutableData alloc] init];
NSURL *url = [NSURL URLWithString:#"http://Someurl.filesCount.php"];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
}
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[receivingData appendData:data];
}
-(void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSError *error = nil;
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
printf("\n the path is :%s",[path UTF8String]);
NSString *zipPath = [path stringByAppendingPathComponent:#"filesCount.php"];
[receivingData writeToFile:zipPath options:0 error:&error];
NSString *documentsDirectoryPath = [[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingPathComponent:#"filesCount"];
NSLog(#"the path %#",documentsDirectoryPath);
}
I made this function in my previous project. You need to pass your imageView and serverUrl, then its automatically show image in your imageView and save image to temp directory, when you want again to fetch same image, then next time it take image from disk.
+(void)downloadingServerImageFromUrl:(UIImageView*)imgView AndUrl:(NSString*)strUrl{
NSFileManager *fileManager =[NSFileManager defaultManager];
NSString* theFileName = [NSString stringWithFormat:#"%#.png",[[strUrl lastPathComponent] stringByDeletingPathExtension]];
NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"tmp/%#",theFileName]];
imgView.backgroundColor = [UIColor darkGrayColor];
UIActivityIndicatorView *actView = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[imgView addSubview:actView];
[actView startAnimating];
CGSize boundsSize = imgView.bounds.size;
CGRect frameToCenter = actView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
actView.frame = frameToCenter;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSData *dataFromFile = nil;
NSData *dataFromUrl = nil;
dataFromFile = [fileManager contentsAtPath:fileName];
if(dataFromFile==nil){
dataFromUrl=[[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:strUrl]] autorelease];
}
dispatch_sync(dispatch_get_main_queue(), ^{
if(dataFromFile!=nil){
imgView.image = [UIImage imageWithData:dataFromFile];
}else if(dataFromUrl!=nil){
imgView.image = [UIImage imageWithData:dataFromUrl];
// NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"tmp/%#",theFileName]];
BOOL filecreationSuccess = [fileManager createFileAtPath:fileName contents:dataFromUrl attributes:nil];
if(filecreationSuccess == NO){
NSLog(#"Failed to create the html file");
}
}else{
imgView.image = [UIImage imageNamed:#"NO_Image.png"];
imgView.tag = 105;
}
[actView removeFromSuperview];
[actView release];
});
});
}
Try using this code.
NSURL* url = [NSURL URLWithString:#"http://imageAddress.com"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
// do whatever you want with directory or store images.
NSImage* image = [[NSImage alloc] initWithData:data];
}
}];
Use this code to download the image using URL and store in document directory. Iterate the logic for set of images to download and store.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *imageURL = #"http://sampleUrl";
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"Image1.png"]];
if(image != NULL)
{
//Store the image in Document
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile: imagePath atomically:YES];
}
If you'll showing the images loaded from server on UI, then
try SDWebImageView, can be used with UIButton or UIImageView, very easy and efficient.
example,
[yourImageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Read how to use it?
Okay, now for multiple images, you may need to run a loop, if you've a common base url for all your pictures (on server) something like http://yoururl/server/pictures/1.png will be replace by 2.png 3.png ... n.png, or you get different urls for pictures need to pass that url, you can load it into imageview objects, and later save them into document directory (remember, SDWebImageView by default doing this work for you). You can turn this off too.
P.S. It will load images once and stored into local (in cache) it self, next time when you pass the same image url, it won't load from server and directly load the image from local.