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.
Related
I'm trying to upload images to a php file, everything works, but sometimes when the site is
getting slow to load our app start frizzing until the page is done loading so i thought
about using threads to avoid such frizzing, but right now our app display and quit. I dont
see any warnings or problem... it just enter the app and exist right away. Please help me
out
the code:
-(void) source {
NSData *imageData = UIImageJPEGRepresentation(imageView.image, 80);
NSString *urlString = #"http://domain";
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:#"POST"];
our_label.text = [NSString stringWithFormat:#"%#", returnString];
}
-(void)som:(id)param{
[lock lock];
[lock unlock];
}
You can use the NSURLRequest in an asynch method, however if you want your current code to work follow these insturctions
First change this line
[NSThread detachNewThreadSelector:#selector(multithreading:) toTarget: [MainViewController class] withObject:nil];
to
[NSThread detachNewThreadSelector:#selector(multithreading:) toTarget:self withObject:nil];
Second, you are updating the UI in a non UI thread, so wrap this
our_label.text = [NSString stringWithFormat:#"%#", returnString];
around dispatch_get_main_queue like the following
dispatch_async(dispatch_get_main_queue(), ^{
our_label.text = [NSString stringWithFormat:#"%#", returnString];
});
Finally remove the locks since you are not really using them
//[lock lock];
[self source];
//[lock unlock];
Totally wrong approach. Look carefully at the documentation of NSURLConnection, and you will find the method sendAsynchronousRequest:queue:completionHandler:.
You will need to learn using blocks though (but it is worth it).
In addition to yan.kun's answer,
I suggest you to use third party library such as AFNetworking which has all the functionality to deal with Async operation which will be more easy to code and safe.
link : https://github.com/AFNetworking/AFNetworking/
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 kinda want a CMS feature in my iPhone app. I want to load text and an image from the internet and apply them to a UIImageView, UIButton, and UITextView. However I am getting an error. Currently the code works fine with the loading of the image. I had the text code in the viewDidAppear code however the user wasn't able to interact with the screen until the text loaded so I moved it to the loadInfo method with the Image, however it didn't like this and gave me a Bad Access error. And printed the following in the console:
2011-05-08 21:26:13.770 Fraction Calculator Lite[2184:6b03] bool _WebTryThreadLock(bool), 0x62338d0: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
Anyone know what might be my issue?
Thanks!
This is my code:
- (void)viewDidAppear:(BOOL)animated {
[self performSelectorInBackground:#selector(loadInfo) withObject:nil];
}
-(void) loadInfo {
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
NSError *error;
NSString *internetTest = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://lindahlstudios.com/cms/internettest.txt"] encoding:NSUTF8StringEncoding error:&error];
if ([internetTest isEqualToString: #"Internet Connection Complete"]) {
UIImage *img1 = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://lindahlstudios.com/cms/adimage.png"]]];
[image1 setImage:img1];
[img1 release];
NSString *string = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://lindahlstudios.com/cms/adtext.txt"] encoding:NSUTF8StringEncoding error:&error];
NSString *string2 = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://lindahlstudios.com/cms/price.txt"] encoding:NSUTF8StringEncoding error:&error];
[adText setText:string]; //Thread 8: Program received signal: "EXC_BAD_ACCESS"
[price setTitle:string2 forState:UIControlStateNormal];
}
[arPool release];
}
You cannot invoke UIKit operations on background thread. I would suggest you get the data on background thread and then update it in the main thread.
Use blocks for ease in coding,
Once you retrieve NSData in background thread, simple assign it to the UIImage etc in the main thread via blocks,
//get NSData from URL
dispatch_async(dispatch_get_main_queue(), ^{
//set assignments for UIImage here.
});
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.
Hey all. I'm really new at this obj-c/xcode stuff. I'm trying to load the background of my xib file. To do this i'm using a UIImageView and populating that with an image I found from the net. the problem with that is that it's REALLY slow. Feels like it's crashing but it's not. I was told to use an NSURLConnection to fix the problem but don't know how. Here's the code i was using previously.
wallpaper.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://mysite.com/mobile/wallpaperR.asp?id=%i",customerID]]]];
How do i translate the above code into the NSURLConnection equivalent?
NSURLConnection will download the data in a new thread, so you app will feel much faster.
It is pretty easy to implement but it does require you to understand the concept of delegation.
I have a useful class I created to handle image downloads on separate threads, I will share the code with you, with comments to let you know what is going on:
AsyncImage.h
AsyncImage.m
If you need help implementing it just leave a comment and I can help you get it working, I remember this used to be pain for me too when I started developing.
You need to do parsing for this as you are using the webservice.Like this
-(void)startParsingForPhotoAlbumList:(NSString*)providerIdString
{
NSString *urlString = [NSString stringWithFormat:#"http://YourUrl/showalbumxml.php?id=%#&show=show",providerIdString];
NSURL *xmlURL = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:xmlURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]autorelease];
NSURLResponse *returnedResponse = nil;
NSError *returnedError = nil;
NSData *itemData = [NSURLConnection sendSynchronousRequest:request returningResponse:&returnedResponse error:&returnedError];
self.xmlParser = [[NSXMLParser alloc] initWithData:itemData];
[xmlParser setDelegate:self];
[xmlParser parse];
}
and need to implement parser's delegate method as an example.
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
if ([[[resultArray objectAtIndex:1]objectForKey:#"Transaction"]isEqualToString:#"Myapp/snaps"])
{
[(LoginViewController *)obj getRegisterResult:resultArray];
}
}
Then in your Viewcontroller access the data,from parsing you need to pass objects,using array or dictionary.
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:itemImagePath]];
UIImage *image = [[UIImage alloc] initWithData:imageData];
There is an example which may help:
NSURLConnection loading image example