In my app I just implement a dispatch_async block that will grab images from a NSDictionary and then eventually when the image is ready, set it to a UITableViewCell UIImageView. What I want to do is make a UIActivityIndicatorAppear in the middle of the UITableViewCell's UIImageView while the dispatch_async is occurring.
This is my code for the dispatch_async block:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^(void) {
NSData *data = [myDictionary objectForKey:#"myKey"];
UIImage *cellImage = [UIImage imageWithData:data];
//we get the main thread because drawing must be done in the main thread always
dispatch_async(dispatch_get_main_queue(),^ {
[cell.imageView setImage:cellImage];
} );
});
Anyway is this possible? And if so, how?
Thanks!
Show the progress indicator before your dispatch_async and remove or hide it your main queue block where you set the image.
Related
I use GCD to download Images from server and updates the processing on UILabel then print the label to the screen (ex: it will print to the screen : "Downloading: 3/15 Images")
But at the beginning the label is : "Downloading: 0/15 Images". Then when it finish downloading, the label is "Downloading: 15/15 Images".The user cant see the download processing.
What I want is user can see the processing like:
"Downloading: 1/15 Images","Downloading: 2/15 Images"."Downloading: 3/15 Images",...,"Downloading: 15/15 Images".
This is my code:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Here your non-main thread.
NSString *text;
for (int i = 0;i<[self.pageImages count];i++){
NSString *image = [self.pageImages objectAtIndex:i];
[dataManage downloadImagesFromUrl: image ];
text = [NSString stringWithFormat:#“Downloading %d/%d”,i,self.pageImages.count];
}
dispatch_async(dispatch_get_main_queue(), ^{
//Here you returns to main thread.
[downloadLabel setText:text];
});
});
Move
dispatch_async(dispatch_get_main_queue(), ^{
//Here you returns to main thread.
[downloadLabel setText:text];
});
inside the for loop so that the UI is updated after each download (rather than only at the end of all iterations).
Try this code inside the dispatch_async block:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[downloadLabel setText:text];
}];
For some reason when I'm downloading my image using GCD, the image will randomly start flickering.
I'll reset the content settings in simulator and it'll work once, then it'll just start flickering again.
This is the code I am using. I've got the reloads in there because if I don't reload it, the image doesn't show until I tap on the cell.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSURL *url = [NSURL URLWithString:self.entries.arrayimage];
NSData *imgData = [NSData dataWithContentsOfURL:url];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
cell.imageView.image = nil;
UIImage *img = [UIImage imageWithData:imgData];
cell.imageView.image = img;
[self.tableView reloadData];
});
});
return cell;
[self.tableView reloadData];
That's because you are using dispatch_async(dispatch_get_global_queue to async load imageData from file. Using this style load image in cellForRow will make cell image should should previous image first. Then finish async load, will call dispatch_sync(dispatch_get_main_queue(), to load the image you want to. Therefore, whenever you reloadData or any other methods to call cellForRow, the cell image will flicker.
I know you want to load image without blocking main thread, but it's not a good way.
Check out apple sample code for Lazy Image loading. And also I checked your code and found that you always downloading image from URL. Instead of that its good to download and save image in caches and then load image from next time in cellForRowAtIndexPath method from local caches if available.
Is this code in cellForRowAtIndexPath:?
If that is the case, the problem here is that all the cells are infinitely reloading the table. You should not be calling reloadData in any of datasource methods that are triggered by reloadData.
What you have is basically an infinite loop of reloading. (reloadData triggers cellForRowAtIndexPath: which once again triggers reloadData).
My suggestion is to use an external component for this as aeskreis posted in his comment.
SDWebImage is probably the best one out there and will allow you to simplify all of the code you have there into simply:
NSURL *url = [NSURL URLWithString:self.entries.arrayimage];
[cell.imageView setImageWithURL:url];
Okay Guys I found out the problem. It was constantly reloading the data which caused te flickering. instead of [self.tableView reloadData] I replaced it with this method:
[cell setNeedsLayout];
I believe this method detects if anything has been changed, and then updates it (from my memory of a couple hours ago so it's probably not 100% accurate), but that fixed my problem.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSURL *url = [NSURL URLWithString:self.entries.arrayimage];
NSData *imgData = [NSData dataWithContentsOfURL:url];
dispatch_sync(dispatch_get_main_queue(), ^{
UIImage *img = [UIImage imageWithData:imgData];
cell.imageView.image = img;
//[self.tableView reloadData];
[cell setNeedsLayout];
});
});
return cell;
I'm loading images from the server in UItableViewCell.
Since Each Image takes 10MB size It cause memory problem.
App crashes Whenever I do scroll over the tableView
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
locationcellObject=[self.tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
NSDictionary *temp= [sortedArray objectAtIndex:indexPath.row];
locationcellObject.title.text=[temp objectForKey:#"locationtitle"];
locationcellObject.subtitle_Lbl.text=[temp objectForKey:#"category"];
NSString *trimmedtitle = [[temp objectForKey:#"locationtitle"]stringByReplacingOccurrencesOfString:#" " withString:#""];
NSString *name=[NSString stringWithFormat:#"images/%#.png",trimmedtitle];
NSString *imageName=[NSString stringWithFormat:#"http://my_URL_HERE/%#",name];
_tempData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]];
UIImage *display=[[UIImage alloc]initWithData:_tempData];
locationcellObject.locationPic_img_View.image=display;
locationcellObject.locationPic_img_View.contentMode=UIViewContentModeScaleAspectFit;
return locationcellObject;
}
Is there any Easy way to do it??
Download your images in background thread, download images with in the block. by this way two thread will be running in your app main thread and background thread. it will be reduces load on main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Call your function or whatever work that needs to be done
//Code in this part is run on a background thread
dispatch_async(dispatch_get_main_queue(), ^(void) {
//Stop your activity indicator or anything else with the GUI
//Code here is run on the main thread
});
});
And also add downloaded images in cache using NSCache,by which next time your images will be loaded from cache,
you can check this link here you can find how to add images in cache
Please refer THIS tutorial. It describes fetching/loading images in UItableView in efficient way.
1 ) you should do the downloading in the background
2 ) make a thumbnail of the image (image with smaller size)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^ {
_tempData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]];
UIImage *display=[[UIImage alloc]initWithData:_tempData];
//make a thumbnail of the image
UIImage *display;
CGSize destinationSize = ...;
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//
dispatch_async(dispatch_get_main_queue(), ^{
//put result to main thread
locationcellObject.locationPic_img_View.image= newImage;
});
});
I am trying to call a method in which I send to the background making use of dispatch_async.
It should be something that is simple, but for some reasons the UI is still blocked until the method returns.
Here is what I have:
dispatch_queue_t privateQueue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(privateQueue, ^
{
__block UIImageView *imgView = [[UIImageView alloc] initWithFrame:self.view.frame];
dispatch_async(dispatch_get_main_queue(), ^
{
imgView = [controllerB startProcess];
controllerC.imageView = imgView;
});
});
I still have to wait for startProcess returns before UI is free again.
Then I tried to move imgView = [controllerB startProcess]; outside of dispatch_get_main_queue():
dispatch_async(privateQueue, ^
{
__block UIImageView *imgView = [[UIImageView alloc] initWithFrame:self.view.frame];
imgView = [controllerB startProcess];
dispatch_async(dispatch_get_main_queue(), ^
{
controllerC.imageView = imgView;
});
});
In this case, the UI is never updated with imgView but UI is not locked up.
I have tried to use a global queue, but the result is the same (UI has to wait):
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
I think I am missing something very obvious here. Either that or it has been long day for me.
EDIT:
In [controllerB startProcess];
I am making use of:
UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);
UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I am not sure if these methods have anything to do with GCD that causes my problem. The image is just .png.
Been thinking hard on this. Am running out of ideas. The only way I can update the UI with the image is to place the method call within dispatch_get_main_queue(), which beats the purpose of using GCD because all UI is blocked until the image is ready and method returns.
Any suggestion would be greatly greatly appreciated.
Use the second approach. Modify startProcess to use completion blocks and update your imageView inside the completion block. This ensures that imageView is updated after startProcess is complete.
Is it possible, that -in your 2nd example- when you try to set the imageView on the main queue the asynchronous calculation of the imageView in the background has not finished yet, so it can't display the imageView?
In that case a dispatch group might help:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.name.queue”, DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
//Do work
imgView = [controllerB startProcess];
});
dispatch_group_notify(group,queue,^{
//This code runs as soon as the code in the dispatch group is done.
controllerC.imageView = imgView;
});
I have a problem with loading an image from an url to display in a table. I currently have the following code to handle the image loading in a class that extends UITableViewCell:
- (void) initWithData:(NSDictionary *) data{
NSDictionary *images = [data objectForKey:#"images"];
__block NSString *poster = [images objectForKey:#"poster"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSURL *posterURL = [[NSURL alloc] initWithString:poster];
NSData *imageData = [NSData dataWithContentsOfURL:posterURL];
if (imageData != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
// 4. Set image in cell
self.backgroundImage.image = [UIImage imageWithData:imageData];
[self setNeedsLayout];
});
}
});
self.backgroundImage.image = [UIImage imageNamed:#"static"];
}
The initWithData method is called from the ViewController in the tableView:cellForRowAtIndexPath: delegate. Everything works as expected until i scroll. From what i read, the TableView cells are recycled and because the images are being loaded async, i get rows with wrong images. Also, the images are not cached and are loaded again whenever the cell is displayed.
Eg: Scroll to the middle and immediately scroll back up. The first cell will have the image that's corresponding to the middle cell that didn't get to finish loading.
Any help or suggestions? Thank you very much :)
First of all as the comment mentioned, I would definitely recommend using an existing framework/component to do this job.
The best candidates are probably:
https://github.com/rs/SDWebImage
https://github.com/enormego/EGOImageLoading
OR if you also want a general networking library
https://github.com/AFNetworking/AFNetworking
That said, if you still want to try it on your own, you would probably want to implement caching with an NSMutableDictionary using the indexPath as the key, and the image as the value.
Assuming you have an initialized instance variable NSMutableDictionary *imageCache
In your cellForRowAtIndexPath method, before attempting to do any image loading, you would check to see if your cache already has an image for this index by doing something like this
if(! imageCache[indexPath])
{
// do your web loading here, then once complete you do
imageCache[indexPath] = // the new loaded image
}
else
{
self.backgroundImage.image = imageCache[indexPath];
}