I want to load too many image after image processing each image.
but when i scroll up and down, lazy loading..
it's my code following.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"FaceImageCell" forIndexPath:indexPath];
UIImageView *fImageView = (UIImageView*)[cell viewWithTag:1];
[faceImageView setImage:[UIImage imageNamed:#"ic_loading"]];
NSLog(#"Loaded Image row : %d", indexPath.row);
ALAsset *asset;
asset = [_imageList objectAtIndex:indexPath.row];
dispatch_async(all_f_d_queue, ^{
ALAssetRepresentation *representation = [asset defaultRepresentation];
NSString *filename = [representation filename];
NSLog(#"%#", filename);
UIImage *image, *fullImage;
if ((fullImage = [_fullImageCache objectForKey:filename]) == nil) {
image = [UIImage imageWithCGImage:[asset thumbnail]
scale:[representation scale]
orientation:(UIImageOrientation)[representation orientation]];
vector<cv::Rect> f = [ImageUtils findFeature:image minsize:MIN_FACE_SIZE
withCascade:face_cascade];
Mat imageMat = [ImageUtils cvMatFromUIImage:image];
fullImage = [ImageUtils UIImageFromCVMat:imageMat];
[_fullImageCache setObject:fullImage forKey:filename];
}
dispatch_async(dispatch_get_main_queue(), ^{
[fImageView setImage:fullImage];
[cell setNeedsDisplay];
});
});
return cell;
}
Due to the many queue, it occured lazy loading.
I want to stop or cancel dispatch queue when loading cell is invisible.
How should I do for detecting cell to be invisible and for stopping dispatch queue??
Let me know please.
I could use asynctask in android. but I don't know how to implement that in iOS.
you can use NSOperation and NSOperationQueue.
It's pretty much the same as dispatchlib but is Object Oriented.
You can wrap the work on on NSOperation and then add that to an NSOperationQueue.
And then you can cancel an NSOperation whenever you want.
Probably have to read Apple documentations for better explanations of these two classes.
Take a look on this tutorial. It shows how you can download data asynchronously using NSOperationQueue and cancel this queue if user did scroll and this cell is not visible anymore.
Related
I am building an app with lots of large image files that are to be displayed in a collection view. Because of the size of the images I've found that it is much quicker to create thumbnails from their URLs, as well as use image caching. When I first implemented this in cellForItemAtIndexPath using GCD I saw a huge reduction in UI lag, but I also noticed that the images in the cells would flicker and change rapidly when the collection view was brought into view and scrolled. I found some other posts about similar issues and they said that checking if the cell is nil first should fix the issue, but unfortunately this seems to create another issue in which many of the images never get loaded. Does anyone know how to fix this?
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
PhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
ObjectWithPhoto *object = self.objects[indexPath.item];
cell.imageView.image = nil;
NSString *imageName = object.imageName;
NSString *imageKey = [NSString stringWithFormat:#"%#_thumbnail", imageName];
if ([[ImageCache sharedCache] imageForKey:imageKey]) {
cell.imageView.image = [[ImageCache sharedCache] imageForKey:imageKey];
} else {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSURL *imageURL = [[NSBundle mainBundle] URLForResource:imageName withExtension:#"jpg"];
CGSize imageSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.width);
UIImage *thumbnail = [UIImage createThumbnailFromURL:imageURL imageSize:imageSize];
[[ImageCache sharedCache] setImage:thumbnail forKey:imageKey];
dispatch_async(dispatch_get_main_queue(), ^(void) {
PhotoCell *cellToUpdate = (id)[collectionView cellForItemAtIndexPath:indexPath];
if (cellToUpdate) {
cellToUpdate.imageView.image = thumbnail;
} else {
NSLog(#"cell is no long visible");
}
});
});
}
return cell;
}
Maybe you're satisfied with your solution, but I'm not. I think at least one source of weirdness you were seeing is from not clearing the image (or setting it to a placeholder) in the case where the image you need isn't cached. Remember, as soon as you begin scrolling, the images won't be in the cache but the reused cell's images will be set -- and wrongly so, to images for other indexPaths. So, fix one...
if ([[ImageCache sharedCache] imageForKey:imageKey]) {
cell.imageView.image = [[ImageCache sharedCache] imageForKey:imageKey];
} else {
// fix one: clear the cell's image now, if it's set, it's wrong...
cell.imageView.image = nil; // or a placeholder
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
// ...
Second, I throw a yellow flag whenever I see somebody call their own cellForItem datasource method and poke values into the cell. This is more concise and more polite....
cell.imageView.image = nil; // or a placeholder
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSURL *imageURL = [[NSBundle mainBundle] URLForResource:imageName withExtension:#"jpg"];
CGSize imageSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.width);
UIImage *thumbnail = [UIImage createThumbnailFromURL:imageURL imageSize:imageSize];
[[ImageCache sharedCache] setImage:thumbnail forKey:imageKey];
dispatch_async(dispatch_get_main_queue(), ^(void) {
// fix two: don't get the cell. we know the index path, reload it!
[collectionView reloadItemsAtIndexPaths:#[indexPath]];
// deleted evil stuff that was here
});
});
I think I figured out a fix for this. When checking if the cell was still available or nil, it seemed to return nil even when I swear I could still see it on screen. With this in mind I tried telling the collection view to reload data if the cell came back nil and it works! Smooth scrolling and full of non-flickering images.
PhotoCell *cellToUpdate = (id)[self.collectionView cellForItemAtIndexPath:indexPath];
if (cellToUpdate) {
cellToUpdate.imageView.image = thumbnail;
} else {
NSLog(#"cell is no long visible");
[self.collectionView reloadData];
}
im making a tableview loaded with some NSArrays, the cell contains two labels and a background image view loaded with a URL image. The problem is that the scrolling of the tableview is slow, its like freeze or something.... i think that is because of the Imageview but what can i do???
heres some of my code so you can see:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return _Title.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
int row = [indexPath row];
cell.TitleLabel.text = _Title[row];
cell.DescriptionLabel.text = _content[row];
cell.CellImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:_ImageUrl[row]]]];
cell.CellImageView.contentMode = UIViewContentModeTop;
cell.CellImageView.contentMode = UIContentSizeCategoryMedium;
cell.backgroundColor = [UIColor clearColor];
return cell;
}
For extra info this tableview presents a Detailviewcontroller with this same info.
Load the image asynchronously(load it as the images come in). Look into great classes such as SDWebImage https://github.com/rs/SDWebImage
You are blocking the main thread with the following line of code:
cell.CellImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:_ImageUrl[row]]]];
My suggestion would be to use AFNetworking and replace the code with the following:
[cell.cellImageView setImageWithURL:[NSURL URLWithString:#"http://example.com/image.png"]];
Also, your pointers should start with a lower case letter. For instance, CellImageView should be cellImageView.
You are calling [NSData dataWithContentsOfURL:[NSURL URLWithString:_ImageUrl[row]]] from cellForRowAtIndexPath , which is not good idea. Because it'll go to the server every time a UITableViewCell is loaded.
You must use Asynchronous image loading & cache. These libraries might help you : (My personal favourite is SDWebImage)
1) https://github.com/rs/SDWebImage
2) https://github.com/nicklockwood/AsyncImageView
And more, you can refer to the sample code by Apple about LazyTableImages - https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html
UPDATE:
For SDWebImage follow this guide. It's very good.
http://iosmadesimple.blogspot.in/2013/04/lazy-image-loading.html
To expand on #vborra's answer - the reason why your tableview is slow is that (in your code) the entire image MUST have finished downloading before it displays.
This is because dataWithContentsOfURL: is a synchronous method. You need to use asynchronous APIs to download images in the background and when they've finished downloading, display them on the screen.
Here is a code snippet from the github page adapted for your example. Make sure you add the folder SDWebImage and it's contents from https://github.com/rs/SDWebImage to your project.
#import <SDWebImage/UIImageView+WebCache.h>
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
int row = [indexPath row];
cell.TitleLabel.text = _Title[row];
cell.DescriptionLabel.text = _content[row];
[cell.CellImageView setImageWithURL:[NSURL URLWithString:_ImageUrl[row]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.CellImageView.contentMode = UIViewContentModeTop;
cell.CellImageView.contentMode = UIContentSizeCategoryMedium;
cell.backgroundColor = [UIColor clearColor];
return cell;
}
Note, if you're downloading images of all different sizes, you may end up with resizing issues which will also decrease your performance. The SDWebImage page has a link to an article to help you with this problem if you come across it. http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/
You may also experience a performance bump when using transparency and tableviews, but it depends on the rest of your code.
AFNetworking is an excellent tool to use, but might be overkill if you're not using networking anywhere else in your app.
load your image like this
//get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:mapUrlStr]];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
cell.CellImageView.image = [UIImage imageWithData:imageData]
});
});
Hi my problem is that when I scroll TableView the image will appear in a wrong cell, after a few seconds the correct image appears.
here is my code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
}
[cell setOpaque:NO];
[cell setBackgroundColor: [UIColor clearColor]];
PlaceData *data = [tableData objectAtIndex:indexPath.row];
UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];
UILabel *sciNameLabel = (UILabel *)[cell viewWithTag:200];
UIImageView *thumbnailImageView = (UIImageView *)[cell viewWithTag:300];
nameLabel.text = data.name;
sciNameLabel.text = data.scientific_name;
// get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSURL *urlToPicture = [NSURL URLWithString:[NSString stringWithFormat:#"%#", data.thumbnail]];
NSData *imgData = [NSData dataWithContentsOfURL:urlToPicture options:0 error:nil];
// This will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
thumbnailImageView.image = tmpImage;
//dispatch_release(concurrentQueue);
});
});
return cell;
}
please help me
You can try adding following code to your cellForRowAtIndexPath -
1) Assign an index value to your custom cell. For instance,
cell.tag = indexPath.row
2) On main thread, before assigning the image, check if the image belongs the corresponding cell by matching it with the tag.
dispatch_async(dispatch_get_main_queue(), ^{
if(cell.tag == indexPath.row) {
UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
thumbnailImageView.image = tmpImage;
}});
});
You are reusing old cells, which is a good thing. However, you are not initializing the image in the cell's image view, which is not such a good thing. What you're describing happens because an old cell, with an image that was already loaded for that cell, is used for the new cell. You are then loading that cell's image in the background (which, again, is good) but it takes a few moments for that image to fully load. In the meantime, the image that was already loaded on the old cell, is displayed (and that's the reason you're seeing a wrong image in that cell, for a few moments).
The solution? add either
thumbnailImageView.image = nil
or
thumbnailImageView.image = someDefaultImageWhileYourRealOneLoads
right before dispatch_queue_t concurrentQueue ....
That way, you won't see the old (irrelevant) image while the real one loads.
I hope this helps. Good luck.
As becauase your ImageView is being loaded in an async dispatch call which is NOT on the main thread and is being called in some other thread so there is a delay in fetching the data from the URL and then converting it to an UIImage. THis process takes a bit of time as you know but you are scrolling the tableview in a faster rate. And as you know cellForRowAtIndexPath reuses any cell that is out of the window so the cell that is being reused might NOT fetched the imagedata that it WAS TO RETRIEVE previously when it was in the Window. Thus it loads the wrong data and then again when async is fired for that specific cell the cell loads that image but there comes the delay.
To overcome this feature as Chronch pointed it out u can leave the imageview as nil OR you can use AFNetworking's own UIImageView catagory which has a superb class to load imageview images quite elegantly
I'll leave u a link to it AFNetworking
I would do all my data binding at - tableView:willDisplayCell:forRowAtIndexPath: only because at cellForRowAtIndexPath your cell hasn't been drawn yet.
Another solution you can use is AFNetworking like someone else mentioned before me.
Swift 3.0
DispatchQueue.main.async(execute: {() -> Void in
if cell.tag == indexPath.row {
var tmpImage = UIImage(data: imgData)
thumbnailImageView.image = tmpImage
}
})
cell.thumbnailimages.image=nil
cell.thumbnailimages.setImageWith(imageurl!)
I think these two lines solve your problem.
I have a table view that loads title and images from url address. Since I added the images, the scrolling isn't smooth. I have changed the background to clear. The images are low resolution. I prefer accessing them with url, not to downloaded the image and re-upload it on the app. Looking forward for your solutions to make the scrolling smooth. Thanks
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TableCell" forIndexPath:indexPath];
NSString *str = [[feeds objectAtIndex:indexPath.row] objectForKey: #"title"];
[[cell textLabel] setNumberOfLines:0]; // unlimited number of lines
[[cell textLabel] setFont:[UIFont systemFontOfSize: 16.0]];
cell.backgroundColor = [UIColor clearColor];
cell.contentView.backgroundColor = [UIColor clearColor];
cell.TitleLabel.text=str;
UIImage *pImage=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:feeds2[indexPath.row]]]];;
[cell.ThumbImage setImage:pImage];
return cell;
}
replace your code in cellForRowAtIndexPath:
after this line cell.TitleLabel.text=str;
That way you load each image in the background and as soon as its loaded the corresponding cell is updated on the mainThread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSData *imgData = NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:feeds2[indexPath.row]]];
if (imgData) {
UIImage *image = [UIImage imageWithData:imgData];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
UIImage *image = [UIImage imageWithData:imgData];
if (image) {
cell.ThumbImage.image = image;
}
});
});
A better approach is to cache the image , so you dont need to download them each time , the table scroll.
here are some very good references to accomplish this.
LazyTableImages Reference
SDWebImage
UIImageView+AFNetworking
Answer is simply you have to implement the loading with NSOperation where a custom class to handle your download and have your NSOperationQueue as downloadQueue. Every UITableView is a (sub class) UIScrollView therefore you can use the methods directly.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[downloadQueue cancelAllOperations]; // clear your queue
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
// start download only for visible cells
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// start download only for visible cells
}
for more detail information visit this tutorial. There you can really find good solution for your need.
In my Core Data model I have an entity which keeps URLs of images. I want to download these images so I can use them in my UICollectionView model, which is an array of my Core Data entities. I want to always be able to access these images synchronously (assuming they have already been downloaded async) so there's no delay between them loading in their respective cell.
Currently, I am using an async method in the cellForIndexPath: data source delegate method of the UICollectionView but when the collection view is reloaded, if there are images still being downloaded, they get assigned to the wrong cell as cells have been inserted during this time.
This problem seems like it should be very obvious to solve but I cannot work it out.
Any ideas are greatly appreciated.
Thanks.
You can download the images asynchronously in viewDidLoad, and add them to an NSMUtableArray as follows
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < carPhotos.count; i++) {
NSURL *photoURL = [NSURL URLWithString:carPhotos[i]];
NSData *photoData = [NSData dataWithContentsOfURL:photoURL];
UIImage *image = [UIImage imageWithData:photoData];
[carImages addObject:image];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
});
And then check in the cellForRowAtIndexpath to ensure that the indexPath matches the arrayIndex, and if you want load a dummy image for not-loaded images, as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CarsAppCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.carMakeLabel.text = carMakes[indexPath.row];
cell.carModelLabel.text = carModels[indexPath.row];
if (carImages.count > indexPath.row) {
cell.carImage.image = carImages[indexPath.row];
} else {
cell.carImage.image = [UIImage imageNamed:#"dummy.png"];
}
return cell;
}
I hope it helps....
A couple of things you can do:
In the cell's prepareForReuse you can cancel any (still) pending image request.
In the completion process for the image request you can verify that the image url is still as expected before setting the image.
Something like:
-(void)prepareForReuse
{
[super prepareForReuse];
_imageUrl = nil;
}
-(void)useImageUrl:(NSString*)imageUrl
{
_imageUrl = imageUrl;
if(_imageUrl)
{
__weak typeof(self) weak = self;
[UIImage fetchRemoteImage:imageUrl completion:^(UIImage* image){
if(weak.imageUrl && [weak.imageUrl isEqualToString:imageUrl] && image) {
weak.imageView.image = image;
}
}];
}
}