spinner not shown when downloading an uimageview from url IOS - ios

When moving from one view to another, I want in my second view as long as the image is being downloaded to see a spinner (so that the second view will open instantly not take any time at all, and the user waits for the spinner to finish).
Here is my code:
In the second View, the one that opens:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIActivityIndicatorView *spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center=CGPointMake(160.0,240.0 );
spinner.hidesWhenStopped=YES;
[self.view addSubview:spinner];
[spinner startAnimating];
NSString *urlString=[NSString stringWithFormat:#"http://mysite.com/projects/test/test.jpg"];
NSLog(#"URL is:%#",urlString);
NSURL *url=[NSURL URLWithString:urlString];
[more_info_image setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:url]]];
[spinner stopAnimating];
}
I do not see the spinner at all. i do not think that this is because my internet connection is so fat. On the other hand, I see a little delay when a button is pressed and waiting for the view to open - like that it is that time when it tries to download the image.
I want to open the view, have the spinner, download the image and then let the spinner go away.
Must I change anything?

You might not be seeing the UIActivityIndicatorView since you are using [NSData dataWithContentsOfURL:url] on the main thread. This is blocking the main thread from displaying the UIActivityIndicatorView until after the image is downloaded, and by that point you are removing it.
You might want to do something like:
but make sure you define spinner in your *.h file for this to work.
- (void)viewDidLoad
{
...
//This code will make downloadImage run in the background thread
[self performSelectorInBackground:#(downloadImage) withObject:nil]
...
}
- (void) downloadImage{
NSLog(#"URL is:%#",urlString);
NSURL *url=[NSURL URLWithString:urlString];
UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
//This code will make setImage run in the main thread since it is changing UI
[more_info_image performSelectorOnMainThread:#selector(setImage:) withObject:downloadedImage waitUntilDone:NO];
//This code will make stopAnimating run in the main thread since it is changing UI
[spinner performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
}
The other way is to use Grand Central Dispatch and do something like:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIActivityIndicatorView *spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center=CGPointMake(160.0,240.0 );
spinner.hidesWhenStopped=YES;
[self.view addSubview:spinner];
[spinner startAnimating];
//This is the new GCD code
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
//This code will run on a background thread
NSString *urlString=[NSString stringWithFormat:#"http://mysite.com/projects/test/test.jpg"];
NSLog(#"URL is:%#",urlString);
NSURL *url=[NSURL URLWithString:urlString];
UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
dispatch_sync(dispatch_get_main_queue(), ^{
//this code runs on the main thread since it is UI changes
[more_info_image setImage:downloadedImage];
[spinner stopAnimating];
});
});
}

As you are downloading your image in the viewDidLoad method so while the image is downloading it did not loads your view so that's why you are experiencing a delay try this
in your header file add an instance of UIActivityIndicator
UIActivityIndicator *spinner;
in your implementation file do something like this
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center=CGPointMake(160.0,240.0 );
spinner.hidesWhenStopped=YES;
[self.view addSubview:spinner];
[spinner startAnimating];
[self performSelector:#selector(downloadYourImageMethod) withObject:nil afterDelay:1];
}
-(void)downloadYourImageMethod
{
NSString *urlString=[NSString stringWithFormat:#"http://mysite.com/projects/test/test.jpg"];
NSURL *url=[NSURL URLWithString:urlString];
[myIMageview setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:url]]];
[spinner stopAnimating];
}

modify your code as
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIActivityIndicatorView *spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center=CGPointMake(160.0,240.0 );
spinner.hidesWhenStopped=YES;
[self.view addSubview:spinner];
[spinner startAnimating];
[self performSelector:#selector(downloadImage) withObject:nil afterDelay:0.1];
}
-(void)downloadImage
{
NSString *urlString=[NSString stringWithFormat:#"http://mysite.com/projects/test/test.jpg"];
NSLog(#"URL is:%#",urlString);
NSURL *url=[NSURL URLWithString:urlString];
[more_info_image setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:url]]];
[spinner stopAnimating];
}

Sometimes, it all depends on how you are placing your views. The spinner should the very last thing that's added to the hierarchy in order to be the first in the order when your view loads. This was the issue in my case, I hope this help anyone else that bumps into this answer. :)

Related

Why does loading images asynchronously take forever?

I have a UICollectionView displaying a bunch of images. If I don't load the images asynchronously the scrolling is very choppy and provides a poor user experience. When I load the images asynchronously the scrolling is smooth but it takes a good 5 to 10 seconds to load each image.
Why does it take so long for images to appear when loaded in the background? Here is my code for the background thread which is inside of the cellForItemAtIndexPath delegate:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImageView *bg = (id)self.backgroundView;
UIImageView *selbg = (id)self.selectedBackgroundView;
if (![bg isKindOfClass:[UIImageView class]])
bg = [[UIImageView alloc] initWithImage:thumb];
else
[bg setImage:thumb];
if (![selbg isKindOfClass:[UIImageView class]]){
selbg = [[UIImageView alloc] initWithImage:thumb];
coloroverlay = [[UIView alloc] initWithFrame:selbg.bounds];
[coloroverlay setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[selbg addSubview:coloroverlay];
} else
[selbg setImage:thumb];
[bg setContentMode:UIViewContentModeScaleAspectFill];
[bg setTag: 1];
[coloroverlay setBackgroundColor:[col colorWithAlphaComponent:0.33f]];
[selbg setContentMode:UIViewContentModeScaleAspectFill];
dispatch_sync(dispatch_get_main_queue(), ^{
[self setBackgroundView:bg];
[self setSelectedBackgroundView:selbg];
});
});
EDIT: As #geraldWilliam pointed out, I shouldn't be accessing views from the secondary thread. Here is what I have updated my code to and fixed the issue of images getting set to the wrong cell:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImageView *bg = (id)self.backgroundView;
UIImageView *selbg = (id)self.selectedBackgroundView;
if (![bg isKindOfClass:[UIImageView class]]) bg = [[UIImageView alloc] initWithImage:thumb];
if (![selbg isKindOfClass:[UIImageView class]]){
selbg = [[UIImageView alloc] initWithImage:thumb];
coloroverlay = [[UIView alloc] initWithFrame:selbg.bounds];
[coloroverlay setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[selbg addSubview:coloroverlay];
}
dispatch_sync(dispatch_get_main_queue(), ^{
[bg setImage:thumb];
[selbg setImage:thumb];
[bg setContentMode:UIViewContentModeScaleAspectFill];
[bg setTag: 1];
[coloroverlay setBackgroundColor:[col colorWithAlphaComponent:0.33f]];
[selbg setContentMode:UIViewContentModeScaleAspectFill];
[self setBackgroundView:bg];
[self setSelectedBackgroundView:selbg];
});
});
Most of the code you have here is fine for the main queue. The loading of the image should be on a global queue, but the rest, especially setting the image view's image, should be on the main queue. What's going on in your code is that you're dispatching back to the main queue to set the background view but leaving the assignment of the image property in the background. So, try something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:myImageURL]];
dispatch_sync(dispatch_get_main_queue(), ^{
imageView.image = image;
[self setBackgroundView:imageView];
});
});
I strongly recommend watching WWDC 2012 Session 211: Building Concurrent User Interfaces on iOS, which is the best WWDC session ever. It’s full of clearly presented, practical advice.
Doing stuff with UIImageView off the main queue is worrying and should be fixed, but is probably not the cause of slowness. You haven’t showed us where thumb comes from, which is likely the slow bit.

Hide UIActivityIndicatorView

I am creating many UIImageview using for loop like a wallpaper app. And i am adding UIActivityIndicatorView to each UIImageview. UIImageview load image from the webservice. when the image is loaded to the UIImageview that time the UIActivityIndicatorView of this UIImageview is hide.
I am give tag of both UIImageview and UIActivityIndicatorView.
Please tell me how do this.
Just use a completion block for the image fetching.
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(q, ^{
// Fetch the image from the server
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [[UIImage alloc] initWithData:data];
// Add activity indicatory to image view
[imageview addSubview:activityIndicator];
dispatch_async(dispatch_get_main_queue(), ^{
imageview.image = img;
// After finishing image loading remove activity indicator
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
});
});
if you have a reference to an instance of UIActivityIndicatorView, just remove it from superView:
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];

How to avoid twice adding UIView on not ended block?

I have a "loading" view added to an uiimageview to indicate the image is being loaded, then on a dispatch async I am charging the image and removing the "loading" view once it has finished, problem is that if I call twice this method the "loading" view is added twice and the second one is never removed.
Edited:
on view.h
UIView *loading;
on view.m
loading = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
loading.backgroundColor = [UIColor blackColor];
loading.alpha = 0.8;
loading.layer.cornerRadius = 10;
loading.center = CGPointMake(imageView.frame.size.width/2, imageView.frame.size.height/2);
if (![imageView.subviews containsObject:loading]) {
[imageView addSubview:loading];
}
dispatch_queue_t downloadFoto = dispatch_queue_create("Get Photo", NULL);
dispatch_async(downloadFoto, ^{
UIImage *image = [[UIImage alloc]initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[selectedImage objectForKey:#"url"]]]];
dispatch_sync(dispatch_get_main_queue(), ^{
if (image) {
[imageView setImage:image];
[imageView setNeedsLayout];
if ([imageView.subviews containsObject:loading]) {
[loading removeFromSuperview];
}
}
});
});
If this is called just once or if I call it after the loading is already removed everything works fine, the problem is if I call this before the block had finished.
Thank you guys, this is my solution at the end.
if (!blackView) {
UIActivityIndicatorView *load = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
blackView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
blackView.backgroundColor = [UIColor blackColor];
blackView.alpha = 0.8;
blackView.layer.cornerRadius = 10;
blackView.center = CGPointMake(fotoVisual.frame.size.width/2, fotoVisual.frame.size.height/2);
load.center = CGPointMake(blackView.frame.size.width/2, blackView.frame.size.height/2);
[load startAnimating];
[blackView addSubview:load];
[fotoVisual setImage:[UIImage imageNamed:#"previewImagen.png"]];
descripcionFotoView.text = [selectedImage objectForKey:#"titulo"];
}
if (![fotoVisual.subviews containsObject:blackView]) {
[fotoVisual addSubview:blackView];
}
dispatch_queue_t downloadFoto = dispatch_queue_create("Get Photo", NULL);
dispatch_async(downloadFoto, ^{
[fotoVisual setImageWithURL:[NSURL URLWithString:[selectedImage objectForKey:#"url"]]
placeholderImage:[UIImage imageNamed:#"previewImagen.png"]];
dispatch_async(dispatch_get_main_queue(), ^{
[blackView removeFromSuperview];
blackView = nil;
});
});
dispatch_release(downloadFoto);
It looks like the issue you have is that you are creating a new loading view each time, which is not what you want. Your [imageView.subviews containsObject:loading] will never be true as you make a new loading view each time.
You could change the creation logic to do the checking like this
if (!loading) {
// configure loading
[imageView addSubview:loading];
}
Then modify your call back to something like
dispatch_sync(dispatch_get_main_queue(), ^{
if (image) {
[imageView setImage:image];
[imageView setNeedsLayout];
[loading removeFromSuperview];
loading = nil;
}
});
Is there any specific reason you are using dispatch_sync instead of dispatch_async?
Add you loading subview in dispatch_async block
dispatch_queue_t downloadFoto = dispatch_queue_create("Get Photo", NULL);
dispatch_async(downloadFoto, ^{
if (!loading.superview) {
// we assume your loading view doesn't have a superview so we can add it to imageView
dispatch_sync(dispatch_get_main_queue(), ^{
[imageView addSubview:loading];
};
}
UIImage *image = [[UIImage alloc]initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[selectedImage objectForKey:#"url"]]]];
if (image) {
dispatch_sync(dispatch_get_main_queue(), ^{
[imageView setImage:image];
[imageView setNeedsLayout];
if ([imageView.subviews containsObject:loading]) {
[loading removeFromSuperview];
}
});
}
});
For Async image downloading you can also use SDWebimage
Just download the project and add SDWebimage folder to your project and use as folows
#import "UIImageView+WebCache.h"
[self.imageView setImageWithURL:[NSURL URLWithString:[selectedImage objectForKey:#"url"]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
This will also cache the image.

Continue loading view after method loads from external URL

I have a method to retrieve data from an external url, load it into an array from JSON format, and populate a UITableView. It works fine, but there is no indication to the user that something is happening while the data is downloaded.
- (void)viewDidLoad
{
[super viewDidLoad];
[self retrieveDataC];
}
Here is the code that I tried for viewDidLoad which adds a spinner animation while downloading. I'm attempting to put retrieveDataC on a background thread and when it completes, I would like the view to continue executing as though I didn't implement the multi-threading in the example above.
- (void)viewDidLoad
{
[super viewDidLoad];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center = CGPointMake(160, 240);
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self retrieveDataC];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
});
});
}
The loading spinner displays correctly for a brief moment, however after the process is done I'm left with a blank table as though I have not called [self retrieveDataC] to begin with. Any suggestions, advice? Am I setting up the background process correctly?
Thank you
EDIT:
Here's what ended up working -
- (void)viewDidLoad
{
[super viewDidLoad];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spinner.center = CGPointMake(160, 240);
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self retrieveDataC];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
[self.collectionView reloadData];
});
});
}
Do you call [self.tableView reloadData] after you received the data?

dispatch_queue not executing

i was undergoing stanford cs 193 p assignment5.The problem is that when i segue a data to destination view controller and use dispatch queue to fetch the data in viewDidLoad method.The dispatch_async does'nt execute in the destination view controller .Here is my code for View controller A and destination view controller.
view controller A code
-(void)viewDidLoad
{
[super viewDidLoad];
if(!self.places){
self.spinner.hidesWhenStopped = YES;
self.spinner.center = self.tableView.center;//
[self.view addSubview:self.spinner];
[self.spinner startAnimating];
dispatch_queue_t dispatchQueue = dispatch_queue_create("queue_top_places", NULL);
dispatch_async(dispatchQueue, ^{
self.places = [self getRecentPlacesFromFlicker];
// main queue to load table view data
dispatch_async(dispatch_get_main_queue(), ^{
// load table data
if(self.tableView.window){
[self.tableView reloadData];
[self.spinner stopAnimating];
}
});
});
dispatch_release(dispatchQueue);
}
// Uncomment the following line to preserve selection between presentations.
self.clearsSelectionOnViewWillAppear = YES;
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"Show Recent Photo List"]){
int currentRow = self.tableView.indexPathForSelectedRow.row;
// set up photo list controller model
[segue.destinationViewController setPhotosList:[self.places objectAtIndex:currentRow]];
}
}
Here is my code for destination view controller
- (void)viewDidLoad
{
[super viewDidLoad];
// get the current top place name and fetch photos at that place from flicker
if ([self.photosList isKindOfClass:[NSDictionary class]])
{
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.hidesWhenStopped = YES;
spinner.center = self.tableView.center;//
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_queue_t dispatchQueue1 = dispatch_queue_create("queue_top_50_photos", NULL);
dispatch_async(dispatchQueue1, ^{
self.photosList = [FlickrFetcher photosInPlace:self.photosList maxResults:50];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.tableView.window ){
[self.tableView reloadData];
[spinner stopAnimating];
}
});
});
dispatch_release(dispatchQueue1);
}
// Uncomment the following line to preserve selection between presentations.
self.clearsSelectionOnViewWillAppear = YES;
self.title = #"50PhotoList";
}
The condition ([self.photosList isKindOfClass:[NSDictionary class]]) is satisfied checked with a debugger
I was just thinking about this, have you tried running this code in viewDidAppear instead of viewDidLoad? I'd give that a try.
Any reason why you create a new queue for each operation? What if you remove the dispatch_release instruction at the end? Maybe your async operation takes longer than the time it gets for your queue to get released.
Try to use a global queue instead of one that you create yourself, one you don't have to release when you're done.
Replace
dispatch_queue_t dispatchQueue1 = dispatch_queue_create("queue_top_50_photos", NULL);
with
dispatch_queue_t dispatchQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
and remove the dispatch_release(dispatchQueue1);
dispatch_async returns immediately and performs the task in background and executes rest of the code in the viewDidLoad or other viewDidAppear methods.
One solution would be to use dispatch_sync which waits for the task to execute and to nest it in dispatch_async like this
dispatch_queue_t dispatchQueue1 = dispatch_queue_create("queue_top_50_photos", NULL);
dispatch_async(dispatchQueue1, ^{
dispatch_queue_t dispatchSyncQueue = dispatch_queue_create("queue_top_50_photos", NULL);
dispatch_sync( dispatchSyncQueue,^){
self.photosList = [FlickrFetcher photosInPlace:self.photosList maxResults:50];
});
dispatch_sync(dispatch_get_main_queue(), ^{
if (self.tableView.window ){
[self.tableView reloadData];
[spinner stopAnimating];
}
});
dispatch_release(dispatchSyncQueue);
});
dispatch_release(dispatchQueue1);

Resources