The iPhone SDK often makes doing things very easy, as long as you know where to look. Loading an Image into an UIImageView is one of these things. The problem I ran into, however, was that loading the image took too much time when performing the operation synchronously. To my surprise it wasn't much harder to load the image on a background thread. This was easily accomplished using NSOperation, specifically the prebuilt subclass NSInvocationOperation.
In a typical situation you might be downloading an image from somewhere on the internet, in this case Flickr. Now this might be a small image, in which case it may not take that long. But that still depends on internet connection, site, and many other factors. So, if you wanted to code it up quick and dirty you might go with something like the following code, which will download the image synchronous and create an image from that data. Finally, it sets the image view's image property to the new image. Downloading the image blocks all user interaction and interface updating, not good for the user's experience.
UIImage* image = [[UIImage alloc] initWithData:imageData];
[imageView setImage:image]; // UIImageView
[image release];
[imageData release];
Now this is fine if you don't care about performance. But if you want to have your application running smoothly you going to want to download this image in the background, so the rest of your application can go ahead and do what it needs to. To do this we are going to use NSInvocationOperation which allows us to run a method on a background thread. In order to use this object we also have to have a NSOperationQueue to add the invocation object to. Basically how it works is you tell the program what method you want to run and add it to a queue which is running on its own thread. The queue then runs the invocations that were added to it, in order, of course. Really all you need to know though to use it is that you need to create a queue, invocation object, and add it to the queue.
The asynchronous method takes a little bit more code but not much more. We start by creating the NSOperationQueue and NSInvocationOperation. The invocation takes in the target of the operation (who has the method we want to run), a selector for the method we want to run, and the method parameter if needed. Below we have the code for this.
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:@selector(loadImage)
object:nil];
[queue addOperation:operation];
[operation release];
Now you'll see that we are going to call loadImage, which needs to be created. The load method is going to handle downloading the information and creating an image. The last line of the method handles calling a method on the main thread to update the ui - which has to be updated on the main thread. You will notice we do use autorelease for the image this time to make sure it is available when we pass it to the display method.
NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:@"imageurl.jpg"]];
UIImage* image = [[[UIImage alloc] initWithData:imageData] autorelease];
[imageData release];
[self performSelectorOnMainThread:@selector(displayImage:) withObject:image waitUntilDone:NO];
}
The final method we need to create takes in the image to display and sets it on our image view.
[imageView setImage:image]; //UIImageView
}
It really is that easy. Now you can really just use this code to run anything you want on a background thread. You must remember though that you are not allowed to update the ui from anything except the main thread. If you have any questions feel free to drop a comment. Until next time, good coding.
06/30/2010 - 15:43
This is a great tutorial!! I have a question. I have a quiz type app that I am trying to load the images asynchronously through resource files and I can't quite get it to work without loading all the images? It takes too much memory to load all the images (i'm aware of that), but how can I release the images from the superview without causing issues with the random integers? Here's the file - Any help is greatly appreciated!!
index=16;
var_Index=0;
X=160;
R=80;
W1=240;
W2=80;
W3=240;
rgt_ans=60;
wrg_ans=60;
XC=80;
YC=80;
XW1=240;
YW1=80;
XW2=80;
YW2=130;
XW3=240;
YW3=130;
time=15;
[self KBC_Game];
[super viewDidLoad];
}
-(void)KBC_Game{
currentViewLocation = 250;
var_Score=0;
Questions_Count=0;
KBC_Questions=[[NSMutableArray alloc]init];
KBC_Image_Ans=[[NSMutableArray alloc]init];
KBC_Image_Wrong1=[[NSMutableArray alloc]init];
KBC_Image_Wrong2=[[NSMutableArray alloc]init];
KBC_Image_Wrong3=[[NSMutableArray alloc]init];
img_Bankground =[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"BarQuizGameBackground.png"]];
[img_Bankground setUserInteractionEnabled:YES];
[self.view addSubview:img_Bankground];
imageView_tmp = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"prize%d.png"]];
[imageView_tmp setFrame:CGRectMake(60, 200, 200, 100)];
[imageView_tmp setHidden:NO];
[imageView_tmp setImage:[UIImage imageNamed:@"115.png"]];
[self.view addSubview:imageView_tmp];
[imageView_tmp release];
scrol_KBC=[[UIScrollView alloc]initWithFrame:CGRectMake(0,320,320,160)];
scrol_KBC.pagingEnabled = YES;
scrol_KBC.delegate = self;
scrol_KBC.scrollEnabled = YES;
[scrol_KBC setBackgroundColor:[UIColor blackColor]];
[scrol_KBC setContentSize:CGSizeMake(scrol_width*16,scrol_height)];
[scrol_KBC setDirectionalLockEnabled:YES];
[self.view addSubview:scrol_KBC];
img_Bankground =[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"background.png"]];
[img_Bankground setUserInteractionEnabled:YES];
//[scrol_KBC addSubview:img_Bankground];
[img_Bankground release];
HERE IS MY PROBLEM
for(int i=1;i<45;i++){
{
[KBC_Questions addObject:[UIImage imageNamed:[NSString stringWithFormat:@"KBCQ%d.png",i]]];
[KBC_Image_Ans addObject:[UIImage imageNamed:[NSString stringWithFormat:@"KBCC%d.png",i]]];
[KBC_Image_Wrong1 addObject:[UIImage imageNamed:[NSString stringWithFormat:@"KBCW%d.png",i]]];
[KBC_Image_Wrong2 addObject:[UIImage imageNamed:[NSString stringWithFormat:@"KBCWW%d.png",i]]];
[KBC_Image_Wrong3 addObject:[UIImage imageNamed:[NSString stringWithFormat:@"KBCWWW%d.png",i]]];
}
}
arr_Index=[[NSMutableArray alloc]init];
[self random_value];
NSLog(@"arr_Index is %@",arr_Index);
//NSLog(@"size is %d ",[KBC_Questions count]) ;
//NSLog(@"array contents is %@ ",KBC_Questions );
for(int i=0;i<16;i++)
{
//SWAPING ANSWERS
int rand_value=arc4random()%3;
NSLog(@"rand_value is%d",rand_value);
if(rand_value==0)
{
int swap;
swap=XC;
XC=XW3;
XW3=swap;
swap=YC;
YC=YW3;
YW3=swap;
}
if(rand_value==1)
{
int swap;
swap=XC;
XC=XW1;
XW1=swap;
swap=YC;
YC=YW1;
YW1=swap;
}
else if(rand_value==2)
{
int swap;
swap=XC;
XC=XW2;
XW2=swap;
swap=YC;
YC=YW2;
YW2=swap;
}
/* {
UIImageView *imageView;
// create game begin view
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"LetsBegin.png"]];
imageView.center = [self view].center;
imageView.alpha = 0.0;
imageView.transform = CGAffineTransformMakeScale (6.0, 6.0);
[[self view] addSubview:imageView];
// animate it in
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:ANIM_LONG];
imageView.alpha = 1.0;
imageView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
[imageView release];
// animate it out
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:ANIM_LONG];
imageView.alpha = 0.0;
imageView.transform = CGAffineTransformMakeScale (3.0, 3.0);
[UIView commitAnimations];
}
*/
/*
{
CGAffineTransform moveTransform = CGAffineTransformMakeTranslation(-320, 0);
CABasicAnimation *moveAnim = [CABasicAnimation animationWithKeyPath:@"transform"];
moveAnim.duration = 2.0;
moveAnim.toValue= [NSValue valueWithCATransform3D:
CATransform3DMakeAffineTransform(moveTransform)];
moveAnim.repeatCount = 3;
[plane2.layer addAnimation:moveAnim forKey:@"animateTransform"]
}
*/
//NSLog(@"i value is %d",i);
UIImageView *img_Question=[[UIImageView alloc]initWithImage:[KBC_Questions objectAtIndex:[[arr_Index objectAtIndex:i]intValue]]];
//[img_Question setBackgroundColor:[UIColor redColor]];
//img_Question.tag=i;
[img_Question setCenter:CGPointMake(X,23)];
[scrol_KBC addSubview:img_Question];
[img_Question release];
X=X+320;
btn_right=[UIButton buttonWithType:UIButtonTypeCustom];
[btn_right setFrame:CGRectMake(0,0, 125,35)];
btn_right.tag=i;
[btn_right setBackgroundColor:[UIColor redColor]];
//[btn_right setCenter:CGPointMake(var_x_origin_image_right,var_y_origin_image_right)];
[btn_right setCenter:CGPointMake(XC,YC)];
//[btn_right setImage:[KBC_Image_Ans objectAtIndex:[[arr_Index objectAtIndex:i]intValue]] forState:UIControlStateNormal];
[btn_right setImage:[KBC_Image_Ans objectAtIndex:[[arr_Index objectAtIndex:i]intValue]] forState:UIControlStateNormal];
[btn_right addTarget:self action:@selector(right_ans:) forControlEvents:UIControlEventTouchUpInside];
[scrol_KBC addSubview:btn_right];
XC=XC+320;
btn_wrong1=[UIButton buttonWithType:UIButtonTypeCustom];
[btn_wrong1 setFrame:CGRectMake(0,0, 125, 35)];
btn_wrong1.tag=i;
[btn_wrong1 setBackgroundColor:[UIColor redColor]];
[btn_wrong1 setCenter:CGPointMake(XW1,YW1)];
//[btn_wrong1 setImage:[KBC_Image_Wrong1 objectAtIndex:[[arr_Index objectAtIndex:i]intValue]] forState:UIControlStateNormal];
[btn_wrong1 setImage:[KBC_Image_Wrong1 objectAtIndex:[[arr_Index objectAtIndex:i]intValue]] forState:UIControlStateNormal];
[btn_wrong1 addTarget:self action:@selector(wrong_ans:) forControlEvents:UIControlEventTouchUpInside];
[scrol_KBC addSubview:btn_wrong1];
XW1=XW1+320;
btn_wrong2=[UIButton buttonWithType:UIButtonTypeCustom];
[btn_wrong2 setFrame:CGRectMake(0,0, 125, 35)];
btn_wrong2.tag=i;
[btn_wrong2 setBackgroundColor:[UIColor redColor]];
[btn_wrong2 setCenter:CGPointMake(XW2,YW2)];
//[btn_wrong2 setImage:[KBC_Image_Wrong2 objectAtIndex:[[arr_Index objectAtIndex:i]intValue]] forState:UIControlStateNormal];
[btn_wrong2 setImage:[KBC_Image_Wrong2 objectAtIndex:[[arr_Index objectAtIndex:i]intValue]] forState:UIControlStateNormal];
[btn_wrong2 addTarget:self action:@selector(wrong_ans:) forControlEvents:UIControlEventTouchUpInside];
[scrol_KBC addSubview:btn_wrong2];
XW2=XW2+320;
btn_wrong3=[UIButton buttonWithType:UIButtonTypeCustom];
[btn_wrong3 setFrame:CGRectMake(0,0, 125, 35)];
btn_wrong3.tag=i;
[btn_wrong3 setBackgroundColor:[UIColor redColor]];
[btn_wrong3 setCenter:CGPointMake(XW3,YW3)];
//[btn_wrong3 setImage:[KBC_Image_Wrong3 objectAtIndex:[[arr_Index objectAtIndex:i]intValue]] forState:UIControlStateNormal];
[btn_wrong3 setImage:[KBC_Image_Wrong3 objectAtIndex:[[arr_Index objectAtIndex:i]intValue]] forState:UIControlStateNormal];
[btn_wrong3 addTarget:self action:@selector(wrong_ans:) forControlEvents:UIControlEventTouchUpInside];
[scrol_KBC addSubview:btn_wrong3];
XW3=XW3+320;
}
}
-(IBAction)right_ans:(UIButton*)sender
{
Flag_Right_Button_Pressed=1;
[btn_right setEnabled:YES];
scrol_KBC.scrollEnabled = YES;
NSLog(@"Auto_Scroll_Var %d",Auto_Scroll_Var,0);
{
AVsound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"ding2" ofType:@"wav"] ] error:NULL];
AVsound.numberOfLoops=0;
[AVsound play];
}
[imageView_tmp setHidden:NO];
Prize_Count=Auto_Scroll_Var+1;
if(Auto_Scroll_Var+=320)
scrol_KBC.contentOffset= CGPointMake(Auto_Scroll_Var,0);
if(Auto_Scroll_Var==320)
{
[imageView_tmp setImage:[UIImage imageNamed:@"215.png"]];
}
if(Auto_Scroll_Var==640)
{
[imageView_tmp setImage:[UIImage imageNamed:@"315.png"]];
}
if(Auto_Scroll_Var==960)
{
[imageView_tmp setImage:[UIImage imageNamed:@"415.png"]];
}
if(Auto_Scroll_Var==1280)
{
[imageView_tmp setImage:[UIImage imageNamed:@"515.png"]];
}
if(Auto_Scroll_Var==1600)
{
[imageView_tmp setImage:[UIImage imageNamed:@"615.png"]];
}
if(Auto_Scroll_Var==1920)
{
[imageView_tmp setImage:[UIImage imageNamed:@"715.png"]];
}
if(Auto_Scroll_Var==2240)
{
[imageView_tmp setImage:[UIImage imageNamed:@"815.png"]];
}
if(Auto_Scroll_Var==2560)
{
[imageView_tmp setImage:[UIImage imageNamed:@"915.png"]];
}
if(Auto_Scroll_Var==2880)
{
[imageView_tmp setImage:[UIImage imageNamed:@"1015.png"]];
}
if(Auto_Scroll_Var==3200)
{
[imageView_tmp setImage:[UIImage imageNamed:@"1115.png"]];
}
if(Auto_Scroll_Var==3520)
{
[imageView_tmp setImage:[UIImage imageNamed:@"1215.png"]];
}
if(Auto_Scroll_Var==3840)
{
[imageView_tmp setImage:[UIImage imageNamed:@"1315.png"]];
}
if(Auto_Scroll_Var==4160)
{
[imageView_tmp setImage:[UIImage imageNamed:@"1415.png"]];
}
printf("Scrolview Locations>>>%d\n",Auto_Scroll_Var);
if(Auto_Scroll_Var==4480)
{
[imageView_tmp setImage:[UIImage imageNamed:@"1515.png"]];
if(Prize_Count = 1){
[OFAchievementService unlockAchievement:@"375154"];
}
if(Prize_Count > 10){
[OFAchievementService unlockAchievement:@"376024"];
}
if(Prize_Count > 25){
[OFAchievementService unlockAchievement:@"376384"];
}
if(Prize_Count > 50){
[OFAchievementService unlockAchievement:@"376394"];
}
/*
[OFAchievementService unlockAchievement:@"376404"];
*/
{
UIImageView *imageView;
// create game over view
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Brainiac.png"]];
imageView.center = [self view].center;
imageView.alpha = 0.0;
imageView.transform = CGAffineTransformMakeScale (6.0, 6.0);
[[self view] addSubview:imageView];
// animate it in
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:ANIM_LONG];
imageView.alpha = 1.0;
imageView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
}
UIAlertView *alert4 = [[UIAlertView alloc]
initWithTitle:@"You are a brainiac"
message:@"You Have Earned 1 Achievement Point"
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert4 show];
[alert4 release];
}
if(Auto_Scroll_Var==4840)
{
[imageView_tmp setImage:[UIImage imageNamed:@"1515.png"]];
}
}
- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex{
//[self.navigationController setNavigationBarHidden:NO];
//[self.navigationController popViewControllerAnimated:YES];
ScoreController *scoreController = [[ScoreController alloc] init];
[self.navigationController pushViewController:scoreController animated:YES];
[scoreController release];
}
-(IBAction)wrong_ans:(UIButton*)sender
{
if(!(Flag_Wrong_Button_Pressed))
{
NSLog(@"Auto_Scroll_Var %d",Auto_Scroll_Var);
//[imageView_tmp setHidden:NO];
//if(Auto_Scroll_Var<5)
//if(!(Flag_Wrong_Button_Pressed))
{
[imageView_tmp setHidden:NO];
if(Prize_Count>-1)
{
UIImageView *imageView;
// create game over view
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"GameOver.png"]];
imageView.center = [self view].center;
imageView.alpha = 0.0;
imageView.transform = CGAffineTransformMakeScale (6.0, 6.0);
[[self view] addSubview:imageView];
// animate it in
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:ANIM_LONG];
imageView.alpha = 1.0;
imageView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
}
{
AVsound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"buzzer2" ofType:@"wav"] ] error:NULL];
AVsound.numberOfLoops=0;
[AVsound play];
}
{
NSLog(@"0");
[imageView_tmp setImage:[UIImage imageNamed:@"%d"]];
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Seriously?? Start Studying!!"
message:@"Better Luck Next Time!"
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
}
}
}
-(void)random_value
{
int randomIndex = [self getRandomNumber:0 to:19];
for (int c = 0; c < [arr_Index count];c++)
{
// printf("no of times execute....\n%d",c);
if([[arr_Index objectAtIndex:c] intValue]==randomIndex)
{
randomIndex=0;
}
}
if (randomIndex==0)
{
[self random_value];
}
else
{
if(var_Index<16)
{
var_Index++;
[arr_Index addObject:[NSNumber numberWithInt:randomIndex]];
[self random_value];
}
else
{
//printf("Random Value.....\n\n");
//for(int i=0;i<[arr_Index count];i++)
//{
// printf("%d\n",[[arr_Index objectAtIndex:i] intValue]);
//}
return ;
}
}
}
//--------Random Number Manipulation---------
- (int)getRandomNumber:(int)frm to:(int)to
{
return (int)frm + arc4random() % (to-frm+1);
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
NSLog(@"hie");
Flag_Right_Button_Pressed=0;
//X_Axis_ScrollView=X_Axis_ScrollView+320;
Auto_Scroll_Var=Auto_Scroll_Var+=320;
NSLog(@"Auto_Scroll_Var is %d",Auto_Scroll_Var);
NSLog(@"scrol_KBC.contentOffset.x is %f",scrol_KBC.contentOffset.x);
scrol_KBC.scrollEnabled = NO;
[scrol_KBC setDirectionalLockEnabled:YES];
if(scrol_KBC.contentOffset.x < Auto_Scroll_Var)
{
NSLog(@"Hurray");
currentViewLocation = scrol_KBC.contentOffset.x;
scrol_KBC.scrollEnabled = NO;
}
[scrol_KBC setDirectionalLockEnabled:YES];
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[AVsound release];
[super dealloc];
}
@end
08/14/2010 - 12:08
Nice tutorial. Short and to the point, better than Apple's docs (which are usually pretty good). I used the basics to spin out code to access a serial port on a mac that had been slowing down the user interface.
Good show. Thanks!
08/16/2010 - 11:15
This is bad practice. You should download your image data asynchronously in the main thread (e.g. using NSURLConnection's initWithRequest) rather than doing it synchronously in a background thread as you do.
08/16/2010 - 11:35
Okay, but why is it a bad practice?
09/17/2010 - 19:25
He probably does not know why...
11/18/2010 - 06:22
Huh?
He IS downloading the image asynchronously - the whole concept of downloading something asynchronously is to have a separate thread (not the main one) downloading it in the background.
You sir are a moron...
10/12/2010 - 06:43
Hi,
Thanks for the code snippets,
but what if we have multiple images,
can you give some idea for that
same like in app store screen shots.
Thanks
Naren
narender.mudgal@gmail.com
01/15/2011 - 13:00
This is a real good example.
One thing I'd like to do is show a UIActivityIndicatorView spinning with text "loading image....".
Once the image loads, I'd like to stop it from spinning then hide it and the text. It doesn't seem to work if I put at the end of the code as it just executes immediately.
Is there some method that gets invoked when the image finally loads?
Thanks
02/07/2011 - 10:57
Hi...........
This is a surely nice tutorial. But, I am downloading multiple images and animating those images in a image view on main thread. It is working fine in the simulator but I got problem while running that in the Iphone device (Actually i think i'm unable to judge it on simulator).
Thanks
02/07/2011 - 10:59
Can anyone help me..........
06/10/2011 - 18:45
did u ever find a solution? i'm having the same problem!
04/08/2011 - 23:38
Hi, u make it easy to me, thank you. Can You please tell me that how to store image in given folder which u have retrieved from any URL.
04/25/2011 - 03:02
Hi,
Thanks for the great tutorial! It solved my main problem when loading an image in a detail view.
Can you give me some hints (or tips) when I want to put this loadImage code in a separate DataLoader class ? I already got my main data loading methods in this class, but I can't get a 'universal' synchronously image loader to work.
Thanks again!
With regards,
Rutge
06/02/2011 - 18:53
Great tutorial, I was going for a thread to download the images for my app...
But I have a little problem, how do I pass the parameters in the @selector()?
for example, my method:
- (void)saveImage:(UIImage *)image imageName:(NSString *)imageNamethanks....
06/03/2011 - 08:39
You don't.
@selector takes only a function name as a parameter and you can't pass your function parameters.
06/03/2011 - 09:29
How am I supposed to call this for a list of images then?
06/14/2011 - 08:23
Hi, this is just the best tuturial I have seen for this way of use.
By the way, sorry for ma english, I am from Germany.
I hope you can help me too.
I would like to load the image/s to a tableview.
So can I easily do this with this function?!?
I have found many other tuts on the internet, but this one is the shortest and best way of them all, now it would be great if i could load some images with that easy way into a table view.
Is that possible? Could you tell me how? :-)
thanks in advance.
Peter
07/12/2011 - 04:25
You're the best! Thanks so much!
07/21/2011 - 04:17
You saved my day!
Thank you very much... :)
08/02/2011 - 07:45
can any one please tell me how to stop the thread running using nsoperationqueue.
11/23/2011 - 05:04
Thanks so much...!!!
11/30/2011 - 04:22
Thanks This code was really helpful for me...
12/29/2011 - 02:20
This code is causing [NSOperationQueue new]; memory leak.... can someone tell me the solution????
Add Comment
[language] [/language]
Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]
See here for supported languages.
Javascript must be enabled to submit anonymous comments - or you can login.