Images getting mixed up when scrolling in a UITableView - ios

Hi found the answer here: Images getting mixed up in a UITableView - XML parsing
I'm parsing an XML file with links to images which I'm putting into a UITable. For some reason the pictures are getting completely mixed up when I scroll down in the table. Here's the code I'm using for my UITable:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
Tweet *currentTweet = [[xmlParser tweets] objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
CGRect imageFrame = CGRectMake(2, 8, 40, 40);
customImage = [[UIImageView alloc] initWithFrame:imageFrame];
[cell.contentView addSubview:customImage];
}
NSString *picURL = [currentTweet pic];
if (![picURL hasPrefix:#"http:"]) {
picURL = [#"http:" stringByAppendingString:picURL];
}
customImage.image = [UIImage imageWithData:[NSData dataWithContentsOfURL: [NSURL URLWithString:picURL]]];
return cell;
}
Any idea what I'm doing wrong? Any help is seriously appreciated. Thx!

Its because you are dequeueing reusable cells. It is recycling a cell that had a previously used image in it.
I noticed that you are setting the image in the same if block that you are initializing the cell. If I were you I'd move [cell.contentView addSubview:customImage]; to right before your return statement. This way when it recycles a dequeued cell, it won't have an image in it.

Are the images being downloaded from the web?
If so then the delay in downloading the images will be causing this. You'll need to do it in a multi threaded way.
Take a look at this example...
Question about Apple's LazyTableImages sample - Doesn't behave exactly like the app store
There are also many tutorials online about lazy loading with images and UITableViews.

Related

UITableView with huge number of image

My app needs to display huge number of images(about 2000) in a UITableView. Basically, I use the following code to construct my UITableViewCell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
// Some Operations...
NSString *path = [self.dataArray jk_objectWithIndex:indexPath.row];
UIImage *img = [UIImage imageWithContentsOfFile:path];
cell.imageView.image = img;
return cell;
}
This works but when the tableview loads, memory increase fast and it seems that all the images is loaded to the memory.
Is there any good ideas to deal with this? I just want to save the memory.
BTW, anyone knows what the common way is to achieve this need? I think loading all the images to memory is the stupidest way... And the code I initial rows of tableview is the following:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (!_isLoading) {
return self.dataArray.count; // about 2000... That's terrible
} else {
return 0;
}
}
Thanks!
There are two problems with your code.
First, and most important, the images are large but the display of the images in the table is small. That's wrong. You should load the image only at the size you actually need for display.
Second, images are cached by default. You need to prevent the caching of these images as they are loaded.
You can easily do both of those things in your cellForRowAt by using the ImageIO framework.
I figure out a way to this question. Add these lines of code to cellForRowAtIndexPath:
CGRect rectInTableView = [tableView rectForRowAtIndexPath:indexPath];
CGRect rectInSuperview = [tableView convertRect:rectInTableView toView:[tableView superview]];
if ( rectInSuperview.origin.y > SCREEN_HEIGHT || rectInSuperview.origin.y + rectInSuperview.size.height < 0 ) {
cell.imageView.image = self.placeholder;
} else {
NSString *path = [self.dataArray jk_objectWithIndex:indexPath.row];
UIImage *img = [UIImage imageWithContentsOfFile:path];
cell.imageView.image = img;
}
First I check whether the cell is shown on the screen. If yes, the imageView show my data images. If not, it show the placeholder instead. Also, the placeholder is init with [UIImage imageNamed:]. This is the best way because it will be used frequently.

UITableView Scrolling Performance with Custom UITableViewCells

Requirement : To Display upto 9 Datapoints in a single Custom TableViewCell with 2 UILabels,4 UIButtons and 3 UIImages.
Problem Statement:Scrolling with the Custom UITableViewCell is not smooth with these UIImages and UIButtons added as subviews. There are approximately 500 rows in the UITableView.
If the problem is in number of view on one cell.
Try to set shouldRasterize property of cell's layer to YES.
yourCell.layer.shouldRasterize = YES;
yourCell.layer.rasterizationScale = [UIScreen mainScreen].scale;
But remember that all animated views such as UIActivityIndicatorView will force the cell to redraw it's content on each frame.
I use this Library which is just perfect
SDWebImage
You just need to #import <SDWebImage/UIImageView+WebCache.h> to your project, and you can define also the placeholder when image is being downloaded with just this code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
It also cache downloaded images and gives you great performance.
Hope it will help you!

asynchronously loaded images in uitableview disappear on scrolling up

i am able to fetch images asynchronously on to uitableview.i am fetching these images are from a url.on scrolling up uitableview these images disappear and they take time to load again and sometimes they dont load at all.i dont want to use any 3rd party libraries.i dont want to go with synchronous approach.please suggest any correct approach to improve performance.thanks for help in advance.my code is below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier=#"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"identifier"];
}
UIImageView *imgVw=[[UIImageView alloc]initWithFrame:CGRectMake(250, 10, 40, 30)];
[cell.contentView addSubview:imgVw];
Attributes *att = [listOfObjects objectAtIndex:indexPath.row];
strImgUrl=#"http:image url";
strImgName=att.classifiedImg;
if (strImgName == nil) {
UIImage *myImg=[UIImage imageNamed:#"user_circle.png"];
imgVw.image=myImg;
}
else{
strImg=[strImgUrl stringByAppendingString:strImgName];
}
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData *data = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:strImg]];
if ( data == nil )
return;
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *img=[UIImage imageWithData: data];
imgVw.image=img;
});
});
return cell;
}
UITableViews are designed to reuse cells. When you scroll up for example the first cell might get reused to show the 5th cell since the 1st one is now off screen. When you scroll back up cellForRowAtIndexPath is called again, and you are async downloading the same image again. If you want to have it load instantly you will need to cache the images after downloading them the first time so the next time that image is needed you can directly pull it from the cache.
Many third party libraries do this (AFNetworking), but if you don't want to use them, you will have to cache the images manually.
It seems like your reuse code contains bug, which causes everytime to create new cells. The following code uses reuseIdentifier as #"identifier"
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"identifier"];
}
Change it to:
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
}
EDIT:
On reusing tableView cells, you dont need to create and add subviews to cell each time. Instead, just create the subview when creating cell and then if cell is reusing, just get the subview using tag and update your content from datasource.
Your code can be modified like below:
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
UIImageView *imgVw=[[UIImageView alloc]initWithFrame:CGRectMake(250, 10, 40, 30)];
imgVw.tag = indexPath.row;
[cell.contentView addSubview:imgVw];
}
UIImageView imgVw = (UIImageView*)[cell.contentView viewWithTag:indexPath.row];
//Rest is same as you posted.
Hope this will fix the issue.

GCD jumbles the data on scrolling tableview

I'm using grand central dispatcher to load images from server but when i scroll the table the data, i.e. images, jumbles - means 1st image comes to some other place and like wise other images do.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ItemImageCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
cell.selectionStyle=UITableViewCellSelectionStyleNone;
}
NSDictionary *item=[responseDictionary objectAtIndex:[indexPath row]];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
NSString *actionForUser=[item objectForKey:#"action"];
objc_setAssociatedObject(cell,
kIndexPathAssociationKey,
indexPath,
OBJC_ASSOCIATION_RETAIN);
dispatch_async(queue, ^{
if([actionForUser isEqualToString:like])
{
NSURL *url = [NSURL URLWithString:[item objectForKey:#"user_image"]];
NSData *data1 = [[NSData alloc] initWithContentsOfURL:url];
UIImage *image1 = [[UIImage alloc] initWithData:data1];
//userProfileimage
UIButton *userImageButton = [[UIButton alloc] initWithFrame:CGRectMake(10,5, 40,40)];
userImageButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
userImageButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[userImageButton setBackgroundImage:image1 forState:UIControlStateNormal];
[userImageButton addTarget:self
action:#selector(userImageButtonclick:)
forControlEvents:UIControlEventTouchDown];
[cell.contentView addSubview:userImageButton];
}
});
return cell;
}
This is because by the time your async method has finished, cell has been recycled and used for a different index path, so you're updating the wrong cell.
At the point of update, get the cell reference by using the tableview's (not the data source method) cellForRowAtIndexPath: method. This will return the correct cell, or nil if the cell isn't on the screen any more. You can update this cell safely.
You should probably be adding the image data to your model as well so you aren't downloading it repeatedly.
As an example, instead of this line:
[cell.contentView addSubview:userImageButton];
You should have something like this:
UITableViewCell *cellToUpdate = [tableView cellForRowAtIndexPath:indexPath];
[cellToUpdate.contentView addSubview:userImageButton];
There are further problems with your code; you are not caching the images, you will be adding this subview every time this cell comes on screen, and if the cell is reused for a case where it doesn't need the button, the button will still be present. I have only addressed the "GCD jumbling" as described in your question.

Asynchronous image loading in UITableViewCell memory issue

In my iPhone application I have a tableview with custom imageview and loading images from remote location using AsyncImageView class. It works nicely, but one issue is, if I scroll the table, cells will be dequeued and it again trying to get the images from server. So, the method for loading image from AsyncImageView class is calling again and again hence increases the memory allocation, and eventually the app crashes.
Here is my code:
- (UITableViewCell *) getCellContentView:(NSString *)cellIdentifier {
CGRect CellFrame = CGRectMake(0, 0, 300, 40);
CGRect userImageFrame = CGRectMake(5, 7, 36, 36);
UIImageView *userImage;
UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
[cell setFrame:CellFrame];
userImage = [[UIImageView alloc]init];
userImage.frame = userImageFrame;
userImage.tag = 3;
[cell.contentView addSubview:userImage];
[userImage release];
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
cell = [self getCellContentView:CellIdentifier];
else
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget:cell.imageView];
UIImageView *userImage = (UIImageView *)[cell viewWithTag:3];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
NSString *url = [[NSString alloc]initWithFormat:#"%#%#", CommonImageURL,[AllUsersProfileImageArray objectAtIndex:indexPath.row]];
NSURL *imageUrl = [NSURL URLWithString:[url stringByAppendingFormat:#"?%i", rand()]];
[url release];
userImage.image = [UIImage imageNamed:#"defaultPerson.png"];
userImage.imageURL = imageUrl;
return cell;
}
Is there any possible way to fix the issue? Please help.
The best solution will be caching the image that is already downloaded and displaying it from there.
You need to write code for that, or there are some libraries which provide such feature:
HJCache
SDWebImage
The popular AFNetworking library also includes a UIImageView category to load images from the web which is often overlooked. I found it to be quite efficient with respect to memory usage and easy to use.
http://afnetworking.github.com/AFNetworking/Categories/UIImageView+AFNetworking.html
I came across same trouble of memory leaks loading multiple images from server. My application needed fresh images response every time (functionality factor)
I was using NSURLConnection using asynch requests. I tried with
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:0];
[NSURLCache setSharedURLCache:sharedCache]; // in DidFinishLoading & didFailWithError
receivedData=nil; // at didReceiveResponse
receivedData=[[NSMutableData alloc] init];
but nothing really helped , until I removed my cell
[cell removeFromSuperview]; in cellForRowAtIndexPath and initialized it again FOR every new NSURL request (additional if condition on cellForRowAtIndexPath but REALLY that payed off).
Probably the UIImageViews were never removed and new images n data were added constantly as fetched by responses. Removing old UITableViewCell for a new NSURLRequest worked in my case. Hope this helps someone like me lost in cleaning NSURLCache and still memory beefing up.
I posted a custom solution here
Download image asynchronously .
I think it works ok and requires very little code.

Resources