My app is downloading packages of images from a server. It's an array of direct links (20-50 files) from XML.
How do I make sure the whole set of images is fully downloaded?
How do I add a condition to cancel whole download (and remove all already downloaded files) if app was closed with iPhone button? (such methods are in AppDelegate while my whole download code is in some downloadviewcontroller.m)
Anything else I have to worry about while downloading several files? (5-10 MB in total)
The code I currently use isn't very safe in case of download interrupting or app closing. In background thread I'm calling this method for each file:
(BOOL) loadImageFromURL:(NSString *)url withName:(NSString *)filename toFolder:(NSString *)folder {
NSURL *link = [NSURL URLWithString:url];
NSFileManager *manager = [NSFileManager defaultManager];
NSString *filepath = [folder stringByAppendingPathComponent:filename];
if ([manager fileExistsAtPath:filepath]) {
return YES;
}
else
{
UIImage *image = [[UIImage imageWithData:[NSData dataWithContentsOfURL:link]] retain];
NSData *data = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];
if ([data length] <= 0)
[image release];
return NO; // no data
else
{
[data writeToFile:filepath atomically:YES];
[image release];
return YES;
}
}
}
Use nsoperation to do so
check out blow link..
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
now on august 2012 try to search wwdc 2012 video no 211 to learn nsoperation.
u can use block to do so.
[aNsque addExecutionBlock:^{ ...code... }];
where aNsque is nsblockoperation.
Dont use the synchronous call dataWithContentsOfURL. Instead look at how to use the asynchronous method of NSURLConnection, – initWithRequest:delegate:
You can then cancel the request with [connection cancel]; Also you will not have to run it on another thread because it is already asynchronous.
As far as having multiple requests running, you probably have a few options. One idea would be to create an abject that starts the NSURLConnection and parses the response, then create an array of theses objects.
Related
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
iOS download and save image inside app
I want to download a file. I found this
[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://lasp.colorado.edu/home/wp-content/uploads/2011/03/suncombo1.jpg"]]
My questions are:
Where does the file saved?
By using threads, is it any possible to build a status bar for downloads?
Are there any way to change the memory(internal/external) to save the file?
Now I am using
NSData *dataImage = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://lasp.colorado.edu/home/wp-content/uploads/2011/03/suncombo1.jpg"]];
downloadStatus.text =#"size: %zd", malloc_size(dataImage);
The result is always 32. Shouldn't that be the size of the actual image?
The file is not saved. It's loaded/retrieved and converted into an NSData object.
Yes. However, if you're doing this you should look at NSURLConnection and particularly the NSURLConnectionDataDelegate protocol. You'll need to asynchronously download the file and get the callbacks into the delegate to be able to update your status bar. Or you could use a 3rd party networking library to simplify the whole thing, but it's good to understand what's going on behind the scenes.
Yes. You can save the NSData object as a file when it's downloaded. If you were using Cocoa (not iOS) you could use NSURLDownload to download the file directly.
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDownloadProgressDelegate:myProgressIndicator];
[request startSynchronous];
NSLog(#"Max: %f, Value: %f", [myProgressIndicator maxValue],[myProgressIndicator doubleValue]);
And you can also follow this link to make more underastanding :)
http://allseeing-i.com/ASIHTTPRequest/How-to-use
You can assign the downloaded data to a variable and show it in an UIImageView as follows.
NSData *dataImage = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://lasp.colorado.edu/home/wp-content/uploads/2011/03/suncombo1.jpg"]];
self.imageViewYours.image = [UIImage imageWithData:dataImage];
Try This Code Worked for Me. The Image will save to your PhotoAlbum.
-(IBAction)save_Image:(id)sender
{
UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"Your URL"]]]], self, #selector(image:didFinishSavingWithError:contextInfo:), nil);
[self performSelectorOnMainThread:#selector(imageDownloaded) withObject:nil waitUntilDone:NO ];
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
if (error != NULL)
{
// handle error
}
else
{
// handle ok status
}
}
- (void)imageDownloaded
{
// network animation off
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// do whatever you need to do after
}
Since you're new to Obj-C, I'd first get familiar with NSURLConnection and NSURLConnectionDataDelegate (protocol). Once you feel comfortable that you know what's going on, you can easily switch to a networking library such as AFNetworking to simplify things.
I have code that will check to see if an image is on the phone or not (the name being retrieved from the db), and if not, it runs this code:
NSString *urlString = [NSString stringWithFormat: #"http://www.vegashipster.com/%#",image_path];
NSData *imageData = [[NSData alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];
NSLog(#"saving jpg");
UIImage *image = [[UIImage alloc] initWithData:imageData];//1.0f = 100% quality
[UIImageJPEGRepresentation(image, 1.0f) writeToFile:myFilePath atomically:YES];
NSLog(#"saving image done");
NSLog(#"URL String: %#",urlString);
NSLog(#"http://www.vegashipster.com/%#",image_path);
rest_image = [UIImage imageNamed:image_path];
[image release];
[imageData release];
This code works just fine on the simulator, but on the iPhone, the screen freezes for a few seconds (downloading the image, I think), then loads the page, but with no image visible. The next time you hit that page, there is no freeze. So I believe the file is being created, but it's a messed up image file, and therefore not displayed.
I've already broken this code up so that it runs in it's own thread, and again, it works in the simulator. I had thought that if it ran behind the scenes, there would be less of a chance that the image data would get messed up, but the exact same thing happens (minus the freezing). Does anyone know what I am doing wrong with this code? Thanks for any and all help/comments.
Edit
And yes, the images being downloaded are strictly .jpg
Edit 2
I seen:
Make sure you are writing to your DOCUMENTS directory, which you have read+write access to. Otherwise you won't get any files.
at http://www.iphonedevsdk.com/forum/iphone-sdk-development/15628-file-weirdness-files-written-disk-do-not-appear-nsfilemanager-defaultmanager.html . Could this be my issue?
NSString *image_path = [[NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 5)] stringByReplacingOccurrencesOfString:#"../"
NSString *myFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:image_path];
Last Edit
Well, I found what I believe to be my answer at How can I get a writable path on the iPhone? . It pretty much states I cannot save image files where my own image files are located inside the build. If this is incorrect, please let me know and I will try your way.
NSData *imageData = [[NSData alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];
Is a synchronous call, ie it blocks until it has fully executed, which in the case of network operations can be 1 second, 10 seconds or 3 minutes if you don't have a time out. You are presumably running this on the main thread which is why your UI freezes (all UI stuff is done on the main thread so you must do everything not to block it). The reason it doesn't freeze the next time around is probably that it has cached the image data.
You should use asynchronous APIs, NSURLConnection has some, however I strongly recommend ASIHTTPRequest, which is an obj c wrapper around NSURLConnection and co. The code would look something like this (read through the how to use section)
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
UIImage* downloadedImage = [UIImage imageWithData:responseData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
Sorry, forgot about this thread completely. I did end up fixing this issue. Here is what I did:
NSFileManager* fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryDirectory = [paths objectAtIndex:0];
NSString *myFilePath = [libraryDirectory stringByAppendingPathComponent:image_path];
BOOL fileExists = [fileManager fileExistsAtPath:myFilePath];
if (fileExists){
hotel_image = [UIImage imageWithContentsOfFile:myFilePath];
}else{
[NSThread detachNewThreadSelector:#selector(downloadImage:) toTarget:[HotelsDetailsViewController class] withObject:image_path];
fileExists = [fileManager fileExistsAtPath:myFilePath];
if (fileExists){
hotel_image = [UIImage imageWithContentsOfFile:myFilePath];
}else{
hotel_image = [UIImage imageNamed:#"hdrHotels.jpg"];
}
}
In short, I found my app's library directory and saved there.
I'm creating an app which downloads a .zip file from S3 server.
All works fine. Now I want to be able to interrupt the current download. If I could save the current size (bytes) of the file, I would be able to send a new request with a range header for the other part of the file.
Problem lies in the fact that I cannot determine the size of the 'already' downloaded content, because I can only see the file in my directory when the download is completed. So if I interrupt, there isn't a partial file saved.
At this time I use the following code for this:
-(void) downloadFile:(NSMutableArray*)paramArray withDict:(NSMutableDictionary*)options
{
NSLog(#"DOWNLOAD THREAD STARTED");
NSString * sourceUrl = [paramArray objectAtIndex:0];
NSString * fileName = [paramArray objectAtIndex:1];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *newFilePath = [documentsDirectory stringByAppendingString:fileName];
NSError *error=[[[NSError alloc]init] autorelease];
NSURLConnection *fileURL = [NSData dataWithContentsOfURL: [NSURL URLWithString:sourceUrl]];
BOOL response = [fileURL writeToFile:newFilePath options:NSDataWritingFileProtectionNone error:&error];
if (response == TRUE)
{
NSLog(#"DOWNLOAD COMPLETED");
[self performSelectorOnMainThread:#selector(downloadComplete:withDict:) withObject:paramArray waitUntilDone:YES];
}
else
{
NSLog(#"Something went wrong while downloading file.");
NSString *callback = [NSString stringWithFormat:#"downloadInterrupted('%#');",fileName];
[webView stringByEvaluatingJavaScriptFromString:callback];
}
[pool drain];
}
AsiHTTP isn't an option because there are issues with the PhoneGap I'm using.
A better idea is to download the file asynchronously. This has several advantages: The most important one is that your user interface stays responsive. The user can go on using your application while it is downloading and waiting for the data. If the data you are downloading is absolutely essential for the application, display some sort of loading indicator.
You can easily start the asynchronous download via
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:sourceUrl]];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
Now, how do I get the downloades data in an NSData object? You implement the following delegate methods for self:
-connection:didReceiveData:
-connection:didFailWithError:
-connectionDidFinishLoading:
The idea is that you are notified whenever some data drops in through your connection or anything important else happens (success or failure for exmple). So you are going to declare a temporary NSMutableData object as an instance variable (say downloadData) and write to it until the download is complete. Do not forget to initialize the empty object and declare a property as well!
-connection:didReceiveData: is called whenever some sort of data (that is, a part of your downloaded file) arrives. So you are going to append it to your temporary object like this:
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.downloadData appendData:data];
}
Once the download has finished (successfully), the next delegate method is called:
-(void) connectionDidFinishLoading:(NSURLConnection *)connection {
//do whatever you need to do with the data in self.downloadData
}
If the downloads fails, -connection:didFailWithError: is called. You can then save the temporary object, get its size and resume the download later. [self.downloadData length]; gets you the size in bytes of the data in your object.
You are going to have to use a lower level api.
time to read up on unix socket programming. http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm would be a good start.
It really won't be too hard. honest.
I recommend you to build a method that save data chunk every 1, 2 MB or maybe less in order to resume properly your download and avoid memory crash.
This because if you get an error in your transfer maybe your file could be result corrupted.
Anyway send a range HTML header is pretty simple
NSFileHandle *fileHandler = [NSFileHandle fileHandleForReadingAtPath:dataPreviouslySavedPath];
[fileHandler seekToEndOfFile];
unsigned long long int range = [fileHandler offsetInFile];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[request setValue:[NSString stringWithFormat:#"bytes=%lli-", range] forHTTPHeaderField:#"Range"];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
Hope this help you.
I'm making an app that allows you to browse through pictures from a website. I'm currently downloading the images using:
UIImage *myImage = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]];
which works great, but can be time consuming. I start off by downloading 20 images, but I can't do anything until after the 30 or so seconds it takes to download them all.
This one time wait isn't all that bad, but if I want to download the 21st-40th images, I would have to wait another 30 seconds.
Basically, is there a way I can download these images one at a time without holding up any of my animations?
Thanks.
Sure, put the download task in a thread, and use a callback to let your program know when each image is finished. Then you can draw your images as they finish loading, and not hold up the rest of the app. This link has a template that you can use as an example.
Here's a quick and dirty example:
- (void)downloadWorker:(NSString *)urlString
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [[UIImage alloc] initWithData:data];
[self performSelectorOnMainThread:#selector(imageLoaded:)
withObject:image
waitUntilDone:YES];
[image release];
[pool drain];
}
- (void)downloadImageOnThread:(NSString *)url
{
[NSThread detachNewThreadSelector:#selector(downloadWorker:)
toTarget:self
withObject:url];
}
- (void)imageLoaded:(UIImage *)image
{
// get the image into the UI
}
Call downloadImageOnThread for every image you want to load, each will get its own thread, and you'll get calls to imageLoaded as each one completes.
While loading the image on a background thread is definately the solution, I'd use an NSOperation and an NSOperationQueue instead of dealing with the threads yourself (this is the way Apple recommend to deal with threading problems like this!)
The NSOperationQueue will deal with starting/stopping the threads nicely and you can choose how many to run at once etc. It's basically a the same as the other answers but you get a little more control.
There's a tutorial here that looks pretty good.
yeah you can use a secondary thread, and do a lot of work OR you could use things that apple gives us.
NSURLDownload, doesn't "lag" your main thread, You spawn it with a method and you set a endSelector, the endSelector will get called when the download is done.
Spawning a secondary thread for this is not really what you should do.
here you got some code from my app wich does it work perfectly without giving a beach ball of doom.
- (void)downloadAvatar:(NSString *)URL{
NSURL *url = [[NSURL alloc] initWithString:URL];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[url release];
NSURLDownload *download = [[NSURLDownload alloc] initWithRequest:request delegate:self];
NSString *path = [[NSString alloc] initWithFormat:#"%#data/%#.jpg",[[BFAppSupport defaultSupport] bfFolderPath],[[xfSession loginIdentity] userName]];
[download setDestination:path allowOverwrite:YES];
[download release];
[path release];
[request release];
}
- (void)downloadDidFinish:(NSURLDownload *)download{
NSString *path = [[NSString alloc] initWithFormat:#"%#data/%#.jpg",[[BFAppSupport defaultSupport] bfFolderPath],[[xfSession loginIdentity] userName]];
NSData *imageData = [[NSData alloc] initWithContentsOfFile:path];
if( [imageData length] < 10 ){
[self performSelector:#selector(downloadAvatar:) withObject:#"http://media.xfire.com/xfire/xf/images/avatars/gallery/default/xfire160.jpg" afterDelay:0.0];
[imageData release];
[path release];
return;
}
NSImage *theImage = [[NSImage alloc] initWithData:imageData];
[imageData release];
[path release];
[yourImage setImage:theImage];
[theImage release];
}
- (void)download:(NSURLDownload *)aDownload didFailWithError:(NSError *)error{
NSLog(#"Avatar url download failed");
}
The code is a bit ugly, but its not hard to change it as you got the 3 things you need, the method that starts the download and 2 that handle or an error or the finish.
You can also use autoreleased objects some more, but in terms of performance I like using it without autoreleased objects when I can.
I have an application, in which the user will select an image from a UIImagePickerView.
After selecting an image from it, I want to save it in my application.
How can this be achieved?
Thanks in advance for helping me.
Assuming you're using SDK 3.0, here is some code to save the image into your application's documents folder:
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
// Dismiss the picker
[[picker parentViewController] dismissModalViewControllerAnimated:YES];
// Get the image from the result
UIImage* image = [info valueForKey:#"UIImagePickerControllerOriginalImage"];
// Get the data for the image as a PNG
NSData* imageData = UIImagePNGRepresentation(image);
// Give a name to the file
NSString* imageName = "MyImage.png";
// Now, we have to find the documents directory so we can save it
// Note that you might want to save it elsewhere, like the cache directory,
// or something similar.
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
// Now we get the full path to the file
NSString* fullPathToFile = [documentsDirectory stringByAppendingPathComponent:imageName];
// and then we write it out
[imageData writeToFile:fullPathToFile atomically:NO];
return;
}
I would say something like this:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
self.resumePreviousSettingAfterEditing = true;
[self.topImageView setImage:image];
[cubeModel setImage:image forFace:[cubeModel.validFaces objectAtIndex:selectedRowInFacePicker]];
[self dismissImagePickerAnimated:true];
}
You register an event in your controller to handle the image selection. In that event handler, call a method somewhere, say in your model to set the new image. That function would look something like this:
(void)saveImage:(UIImage *)image withName:(NSString *)imageName {
// get the image path
NSString *filename = [self determineImagePath:imageName];
// make sure the file is removed if it exists
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:filename]) {
if(NO == [fileManager removeItemAtPath:filename error:NULL]) {
}
}
// Now, save the image to a file.
if(NO == [UIImagePNGRepresentation(image) writeToFile:filename atomically:YES]) {
[NSException raise:#"Image Save Failed" format:#"Unable to store image %s", filename];
}
}
When you want to load the image again, you would so something like:
- (UIImage *)loadImage:(NSString *)imageName {
NSString *filename = [self determineImagePath:imageName];
NSFileManager *fileManager = [NSFileManager defaultManager];
self.currentImage = nil;
if([fileManager fileExistsAtPath:filename]) {
NSData *imageData = [[NSData alloc] initWithContentsOfFile:filename];
UIImage *image = [UIImage imageWithData:imageData];
self.currentImage = image;
}
return self.currentImage;
}
And don't get me started on transforming which is way harder than it should be.
Enjoy,
Jacob
One thing you will need to address when saving images returned by UIImagePickerVIewController is that writing the data to disk will almost always be unacceptably slow. Your UI will hang while the writing is occurring. So, you should always execute these types of operations in an asynchronous queue. Even if the performance seems good enough for your application when testing, you should still do it an asynch queue -- you never know what other processes the device might have going on which might slow the save down once your app is in the hands of users.
Newer versions of iOS make saving photos asynchronously really, really easy using Grand Central Dispatch (GCD). The steps are:
Create an NSBlockOperation which saves the image
In the block operation's completion block, read the image from disk & display it (the only caveat here is that you must use the main queue to display the image: all UI operations must occur on the main thread).
Add the block operation to an operation queue and watch it go!
That's it. And here's the code:
// Grab the image
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
// Create a block operation with our saves
NSBlockOperation* saveOp = [NSBlockOperation blockOperationWithBlock: ^{
[UIImagePNGRepresentation(image) writeToFile:file atomically:YES];
}];
// Use the completion block to update our UI from the main queue
[saveOp setCompletionBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImage *image = [UIImage imageWithContentsOfFile:file];
// TODO: Assign image to imageview
}];
}];
// Kick off the operation, sit back, and relax. Go answer some stackoverflow
// questions or something.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:saveOp];
Once you are comfortable with this code pattern, you will find yourself using it a lot. It's incredibly useful when generating large datasets, long operations on load, etc. Essentially, any operation that makes your UI laggy in the least is a good candidate for this code. Just remember, you can't do anything to the UI while you aren't in the main queue and everything else is cake.