UITableViewCell load images and reused cells - ios

i need to load from web/files some UIImages. I was searching and i found in other question this code:
if (![[NSFileManager defaultManager] fileExistsAtPath:user.image]) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *imageData =[NSData dataWithContentsOfURL:[NSURL URLWithString:user.imageURL]];
[imageData writeToFile:user.image atomically:YES];
dispatch_sync(dispatch_get_main_queue(), ^{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIImage *image = [UIImage imageWithData:imageData];
[self.imageFriends setObject:image forKey:[NSNumber numberWithInt:user.userId]];
cell.imageView.image = image;
[cell setNeedsLayout];
NSLog(#"Download %#",user.image);
});
});
cell.imageView.image=[UIImage imageNamed:#"xger86x.jpg"];
} else {
NSLog(#"cache");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *image = [UIImage imageWithContentsOfFile:user.image];
//[self.imageFriends setObject:image forKey:[NSNumber numberWithInt:user.userId]];
dispatch_sync(dispatch_get_main_queue(), ^{
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
newCell.imageView.image=image;
[newCell setNeedsLayout];
});
});
}
But the problem is that when i scroll fast to bottom or top the images are loaded wrong and there is a short lag when it ends.
So the question is.. how can i load the UIImages in the correct cell when i use queues to fetch them? Thanks!

I suspect the incorrect images you see are a result of you not setting your place holder image in the event of you having a local copy of an image but still retrieving the local copy asynchronously. Also In the code you appended for loading the local copy you use UIImage a UIKit component on a background thread.
Also interestingly you seem to be doing some kind of UIImage caching. Adding the images to what I assume is an NSMutableArray property named imageFriends. But you seem to have commented out the cache add in the event you have a local copy of the file. Also your posted code never uses the cached UIImages.
While 2 levels of caching seems a bit overboard if you wanted to do this you could do something like this:
UIImage *userImage = [self.imageFriends objectForKey:[NSNumber numberWithInt:user.userId]];
if (userImage) { // if the dictionary of images has it just display it
cell.imageView.image = userImage;
}
else {
cell.imageView.image = [UIImage imageNamed:#"xger86x.jpg"]; // set placeholder image
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:user.image];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = nil;
if (fileExists){
imageData = [NSData dataWithContentsOfFile:user.image];
}
else {
imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:user.imageURL]];
[imageData writeToFile:user.image atomically:YES];
}
if (imageData){
dispatch_async(dispatch_get_main_queue(), ^{
// UIKit, which includes UIImage warns about not being thread safe
// So we switch to main thread to instantiate image
UIImage *image = [UIImage imageWithData:imageData];
[self.imageFriends setObject:image forKey:[NSNumber numberWithInt:user.userId]];
UITableViewCell *lookedUpCell = [tableView cellForRowAtIndexPath:indexPath];
if (lookedUpCell){
lookedUpCell.imageView.image = image;
[lookedUpCell setNeedsLayout];
}
});
}
});
}
UIImages are part of UIKit and not thread safe. But you can load the NSData on another thread.

You are loading image asynchronously, so during fast scrolling cells get reused faster then images are downloaded. One way to avoid loading a wrong image, would be to check if cell already got reused when image is loaded. Or cancel all requests in progress when you dequeue new cells.
I would also recommend to look at AFNetworking, as it contains helpful category for UIImageView, so you can do something like this:
[imageView setImageWithURL:[NSURL URLWithString:#"http://i.imgur.com/r4uwx.jpg"] placeholderImage:[UIImage imageNamed:#"placeholder-avatar"]];
It also contains cancelImageRequestOperation method, to cancel requests in progress. Then your code will look like this:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
} else {
[cell.imageView cancelImageRequestOperation];
}
[cell.imageView setImageWithURL:[NSURL URLWithString:user.imageURL] placeholderImage:[UIImage imageNamed:#"xger86x.jpg"]];

Related

Load and display image one by one from URL and Cache

I am using SDWebImage library to display images in my scrollview with page control.
How to download image one by one and display it. Suppose i have 4 URL : url1,url2,url3,url4
so i want to display url1 first then...2,3,4..so on...2,3,4 should load in background until the are not download a placeholder image show there.
Below is my code of inside for loop i try is it perfect ?
NSString* serverImgicon=<url here in a loop>;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
NSURL *url=[NSURL URLWithString: serverImgicon];
//set your image on main thread.
dispatch_async(dispatch_get_main_queue(),^{
[img sd_setImageWithURL:url
placeholderImage:[UIImage imageNamed:#"placeholderImg"]];
});
});
Try below code, it'll work fine
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CollectionViewCell *cell =[collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
myobject *newobject = _myarray[indexPath.item];
cell.nameLabel.text =newobject.name;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[cell.activityIndicatorForImage startAnimating];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:
[NSURL URLWithString:newobject.image]]];
dispatch_sync(dispatch_get_main_queue(), ^{
if(image!= nil){
[[cell myimage] setImage:image];
[cell.activityIndicatorForImage setHidden:NO];
}else{
UIImage * image = [UIImage imageNamed:#"PLACEHOLDER"];
[[cell myimage] setImage:image];
[cell.activityIndicatorForImage setHidden:YES];
[cell.activityIndicatorForImage stopAnimating];
}
[cell setNeedsLayout];
});
});
return cell;
}
use sdwebCache to load images asynchronously
https://github.com/rs/SDWebImage
/******************/
#import "UIImageView+WebCache.h"
then load images like this :
NSArray *tempArray = [stickersThumbRecords objectForKey:collectioName];
if (tempArray.count>indexPath.item) {
NSURL *url = [NSURL URLWithString:[tempArray objectAtIndex:indexPath.item] ];
[imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:#"placeholderFrame"]];
}

Images are loading in Table View in simulator but not in Device

I'm parsing images from JSON data and displaying in Table View. My code to display images in table view is -
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellidentifier=#"MyCell";
OnlineCell *cell = [tableView dequeueReusableCellWithIdentifier:cellidentifier];
if (cell == nil)
{
cell = [[OnlineCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellidentifier];
}
cell.userLabel.text = [self.allusername objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
imageURL = [self.alluserphoto objectAtIndex:indexPath.row];
img = [UIImage imageWithData:[NSData dataWithContentsOfURL: [NSURL URLWithString:imageURL]]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.userIMage.image = img;
[indicator stopAnimating];
});
});
return cell;
}
Simulator Output -
Device OutPut -
Please tell me the how could i get images in device.
As i can see two screenshot one is Simulator and one is Device that two image common Load at both end simulator and device. So i think issue in to you image that comes from web side. Because you code is correct if there is issue in code then that not load two image as well in device.
Please test with change the image with First loaded image from your back End side and check once that have to load. put first loaded image for all response instead of goggles image that not load in device. i am dame sure issue is in image not in code.
Debugging Suggestion 1: On the device, make sure that the image is accessible. You can try opening the image in Safari. If safari is opening the image, it means that image is accessible.
Debugging Suggestion 2: If the image is accessible, try loading the image synchronously and debug if you're getting the required data (on the device):
NSURL *url = [NSURL URLWithString:self.photoImagePath]; // Is URL valid?
NSData *imageData = [NSData dataWithContentsOfURL:url]; // Is data nil?
UIImage *image = [UIImage imageWithData:imageData]; // Is image nil?
Then, try loading the image asynchronously, like this:
__weak UIImageView *weakImgageView = cell.userIMage;
imageURL = [self.alluserphoto objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL: [NSURL URLWithString:imageURL]]];
dispatch_async(dispatch_get_main_queue(), ^{
weakImgageView.image = image;
[indicator stopAnimating];
});
});
Hope this helps.
The default image is not coming properly. This is what I suspect. Please check if you are giving the name properly or not. This could be the issue.
Set default image until you fetch image asynchronously from JSON.
UIImage *img = [UIImage imageNamed:#"default.png"];
When you fetch image in asynchronously method means it will fetch depend on image size & internet speed. So you just keep a default image until it fetch completely from server
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
OnlineCell *cell = (OnlineCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSArray *nib = nil;
if (cell == nil)
{
nib = [[NSBundle mainBundle] loadNibNamed:#"YourCustomCellNibName" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Your code
cell.userLabel.text = [self.allusername objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//NSData *imagedata = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.alluserphoto objectAtIndex:indexPath.row]]];
NSURL* url = [NSURL URLWithString:[self.alluserphoto objectAtIndex:indexPath.row]];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage* image = [[UIImage alloc] initWithData:data];
// do whatever you want with image
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
OnlineCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
cell.userIMage.image = image;
});
}
}
}];
});
}

Load only visible cells in UITableView

So I have an application that reads records from a database and basically fills out a UITableView with the information from the DB. Some of the information includes an image, which it brings from an online server I have. Everything works fine, but the program is a little slow and scrolling is a little laggy/ jumpy, as its bringing all the images at once, instead of bringing them as I scroll. Is there a way to change it so that as I scroll it brings the next few visible records?
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
static NSString *CellIdentifier = #"VersionCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
}
STSneakerInfo *info = [_versionInfo objectAtIndex:indexPath.row];
cell.textLabel.font = [UIFont boldSystemFontOfSize:14.1];
[[cell textLabel] setNumberOfLines:2];
cell.textLabel.text = [[_uniqueBrand stringByAppendingString:#" "] stringByAppendingString: info.version];
cell.detailTextLabel.textColor = [UIColor grayColor];
cell.detailTextLabel.font = [UIFont boldSystemFontOfSize:14.1];
cell.detailTextLabel.text = info.initialReleaseDate;
NSString *brandVersion = [[_uniqueBrand stringByAppendingString:#" "] stringByAppendingString:info.version];
NSString *underscoredBrandVersion = [brandVersion stringByReplacingOccurrencesOfString:#" " withString:#"_"];
NSString *underscoredBrandName = [_uniqueBrand stringByReplacingOccurrencesOfString:#" " withString:#"_"];
NSData *imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: ([[[[[#"http://www.abc/img/" stringByAppendingString:underscoredBrandName] stringByAppendingString:#"/"] stringByAppendingString:underscoredBrandVersion] stringByAppendingString:#"/"] stringByAppendingString:#"default.jpg"])]];
cell.imageView.image = [UIImage imageWithData: imageData];
return cell;
}
you can use (https://github.com/rs/SDWebImage) to download image async. its easy and fast.
i prefered this library because it will handle your cache.
just write below code
[cell.imageView sd_setImageWithURL: [NSURL URLWithString: ([[[[[#"http://www.abc/img/" stringByAppendingString:underscoredBrandName] stringByAppendingString:#"/"] stringByAppendingString:underscoredBrandVersion] stringByAppendingString:#"/"] stringByAppendingString:#"default.jpg"])]];
you can also download image in background thread as per wenchenHuang answer above. using below code.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString: ([[[[[#"http://www.abc/img/" stringByAppendingString:underscoredBrandName] stringByAppendingString:#"/"] stringByAppendingString:underscoredBrandVersion] stringByAppendingString:#"/"] stringByAppendingString:#"default.jpg"])];
if (data)
{
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
if (img)
cell.imageView.image = img;
});
}
});
Maybe this will help you.
The UITableView class never loads cells until they're about to appear onscreen.
I suggest you to use GCD to download image background.When finished,notice UI to change.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Download images
dispatch_async(dispatch_get_main_queue(), ^{
//Notice UI to change
});
});
Here's a full, real-world example with DLImageLoader
https://github.com/AndreyLunevich/DLImageLoader-iOS/tree/master/DLImageLoader
DLImageLoader is incredibly well-written, maintained constantly, is super-lightweight, and it can properly handle skimming.
It's difficult to beat and is used in many apps with vast numbers of users.
It is really an amazingly well maintained library - and on top of that the new Swift version is the pinnacle of excellence in Swift programming, it's a model for how to do it.
PFObject *aFacebookUser = [self.fbFriends objectAtIndex:thisRow];
NSString *facebookImageURL = [NSString stringWithFormat:
#"http://graph.facebook.com/%#/picture?type=large",
[aFacebookUser objectForKey:#"id"] ];
__weak UIImageView *loadMe = self.cellImage;
[DLImageLoader loadImageFromURL:facebookImageURL
completed:^(NSError *error, NSData *imgData)
{
if ( loadMe == nil ) return;
if (error == nil)
{
UIImage *image = [UIImage imageWithData:imgData];
image = [image ourImageScaler];
loadMe.image = image;
}
else
{
// an error when loading the image from the net
}
}];
another real world example,
-(UICollectionViewCell *)collectionView:(UICollectionView *)cv
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger thisRow = indexPath.row;
BooksCell *cell;
cell = [cv dequeueReusableCellWithReuseIdentifier:
#"CellBooksNormal" forIndexPath:indexPath];
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
// set text items...
cell.title = #"blah;
// set image items using DLImageLoader...
__weak UIBookView *loadMe = cell.anImage;
[DLImageLoader loadImageFromURL:imUrl
completed:^(NSError *error, NSData *imgData)
{
[loadMe use:[UIImage imageWithData:imgData]];
}];
return cell;
}
Reason for your sluggish scroll is that you are downloading image on the main thread.
NSData *imageData = [[NSData alloc] initWithContentsOfURL:
That piece of code which is running on main thread, will make a network call and start downloading contents from server. And the execution halts there until its completed. Which means you wont be be able to scroll down anymore till image is loaded.
There are many workarounds for this. However the logic is same for all. Download image in a separate thread and load it once its completed.
If you are using AFNetworking in your project you can use setImageWithURL: on your UIImageView object. You need to include UIImageView+AFNetworking.h. It has a inbuilt caching mechanism and it will cache the images downloaded for that session.

TableView scrolling stutters after adding images from remote URL

I am building an iOS app that adds XML items to a TableView (among other things, of course). I would like to display the article's thumbnail in the TableView cell or a default placeholder if the article's thumbnail field is empty.
The code below adds the thumbnail from the article but then causes scrolling to stutter quite a bit. If I only use the placeholder image for each cell, everything is fine. I am guessing I am probably not using the most ideal method to add the thumbnail image, not sure if this is causing the problem.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"idCellNewsTitle"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"idCellNewsTitle"];
}
NSDictionary *dict = [self.arrNewsData objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:#"holder-small.jpg"];
cell.textLabel.text = [dict objectForKey:#"title"];
cell.detailTextLabel.text = [dict objectForKey:#"pubDate"];
if ([dict objectForKey:#"thumbnail"] != nil) {
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString: [dict objectForKey:#"thumbnail"]]];
if (imgData) {
UIImage *image = [UIImage imageWithData:imgData];
if (image) {
cell.imageView.image = image;
}
}
}
return cell;
}
Coding in the latest XCode, testing in simulator and on a newer iPod - results are the same with both. No warnings or errors during the running of the app, CPU spikes at 1% when scrolling and memory stays around 16 MB.
UPDATE - Video
Here is a video demonstrating this issue for any future noobs to compare with -
First example is with a placeholder, second is without.
Video on YouTube
replace your code in cellForRowAtIndexPath:
after this line if ([dict objectForKey:#"thumbnail"] != nil)
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: [dict objectForKey:#"thumbnail"]]];
if (imgData) {
UIImage *image = [UIImage imageWithData:imgData];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
UIImage *image = [UIImage imageWithData:imgData];
if (image) {
cell.imageView.image = image;
}
});
});
LazyTableImages Reference
Fetch images using GCD
// Fetch using GCD
dispatch_queue_t downloadThumbnailQueue = dispatch_queue_create("Get Photo Thumbnail", NULL);
dispatch_async(downloadThumbnailQueue, ^{
UIImage *image = [self getTopPlacePhotoThumbnail:photo];
dispatch_async(dispatch_get_main_queue(), ^{
UITableViewCell *cellToUpdate = [self.tableView cellForRowAtIndexPath:indexPath]; // create a copy of the cell to avoid keeping a strong pointer to "cell" since that one may have been reused by the time the block is ready to update it.
if (cellToUpdate != nil) {
[cellToUpdate.imageView setImage:image];
[cellToUpdate setNeedsLayout];
}
});
});

flashing images tableview reuse bug see the video link -->http://www.youtube.com/watch?v=jwEsqjc9sNc&feature=youtu.be

here's the code : i am downloading image from my core data on background and put it on my image view.
static NSString *cellIdentifier = #"Pubs Cell";
PubsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
Pub *current = [self.fetchController objectAtIndexPath:indexPath];
cell.name.text = current.name;
cell.description.text = current.descriptionText;
cell.description.backgroundColor = [UIColor clearColor];
cell.description.editable = NO;
dispatch_queue_t queue = dispatch_queue_create("image_queue", NULL);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#",current.photo]]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.pubImage.image = [UIImage imageWithData:data];
});
});
cell.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"light1.jpg"]];
return cell;
any ideas how to fix that? thanks in advance.
You need to set the image to nil before you start the async loading-task:
// removing the old image from the reused tablecell
cell.pubImage.image = nil;
// load image asynchronously
dispatch_queue_t queue = dispatch_queue_create("image_queue", NULL);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#",current.photo]]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.pubImage.image = [UIImage imageWithData:data];
});
});
This is because loading an image takes some time so you see the old one since tablecells are reused when available.

Resources