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.
Related
I am using asyncimageview in the uitableview for displaying the image in the uitableviewcell but when scrolling the tableview the image are not displayed directly. it first display old one and the present the new image from the url. its not caching or not displaying from cache.
if (Cell == nil) {
Cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
Cell.selectionStyle = UITableViewCellSelectionStyleNone;
//Create Cell Method
[self CreateCell:FollowCell atIndexPath:indexPath]; }
// Set Cell Method
[self SetCell:FollowCell atIndexPath:indexPath];
[Cell setClipsToBounds:YES];
return FollowCell;
// Image code
[asyncImgUser setImageURL:[NSURL URLWithString:strUserImage] andPlaceHolder:[UIImage imageNamed:#"Placeholder.png"]];
}
TableViewCells are reused by TableView for this reason initially old image is shown and after your image is loaded by the async task it displays new image. And if it happens always it means your caching is working.
I am building my cellViews like this:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellIdentifier=#"cell";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
UIImageView cellView = [[UIImageView alloc] initWithFrame:rectCellFrame];
NSError* error=nil;
NSData* imageData = [NSData dataWithContentsOfURL:imageArray[indexPath.row] options:NSDataReadingUncached error:&error];
UIImage* theImage= [UIImage ImageWithData:imageData];
[cellView setImage:theImage];
[cell addSubView:cellView];
.
.
.
.
[cell addSubView:moreViews];
}
Since the loading time (even when the images are cached) is very slow, I need to make this concurrent. But I would like to still be using my code with UIViews/UIImageViews.
Is there a way for me to show a placeholder and when relevant, ie cellView is finished building from all subviews, update the image instead of the placeholder?
Sure. You can set up all the heavy slow code in a asynchronous task. It's often down when images need to be downloaded. I'm sure it's covered in at least 1 of the WWDC videos on Table Views, but I've no clue which one or how old it would be by now.
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// place holder image for the moment
[cellView setImage:placeHolderImage];
// run code to get the real image in asynchronous task
dispatch_async(self.contextQueue, ^{
UIImage *realImage = [thingy imageFromTimeConsumingTask];
// update cell on main thread (you need to do all UI stuff on main thread)
dispatch_async(dispatch_get_main_queue(), ^{
[cellView setImage:realImage];
});
});
}
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.
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.
I want to show an image on the right side in a UITableViewCell.
I'm using this code, to do it:
CGRect imageRect = {80,0,225,44};
UIImageView *uiv = [[UIImageView alloc] initWithFrame:imageRect];
[uiv setImage:[UIImage imageNamed:[NSString stringWithFormat:#"star%#.png", [[self reataurant] valueForKey:#"stars"]]]];
[uiv setClipsToBounds:YES];
[cell addSubview:uiv];
My problem is, that the image is shown in other cells, when i'm scrolling the tableview.
What am i making wrong?
What is probably happening is that you are re-using cells that already have an image when you do not wish to. Check
a) when you re-use a cell, if it has the star image then remove it.
b) before you return the cell add the star at that point (with whatever test you use to determine if the star should appear).
Those UITableViewCells are reused. Look in your cellForRowAtIndexPath for
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
If you only want one or a certain set of cells to have this particular layout, you can pass a different cellIdentifier for these cells.
You can easily add an image view as the accessory view to a table cell for the same effect.
Here is the sample code for your understanding:
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"fileName"]];
cell.accessoryView = imageView;
[imageView release];
And make sure you are reusing cell space of a table view in cellForRowAtIndexPath method by using a cell identifier so that overwritten problem can be eliminated ;)
NSString *CellIdentifier = [NSString stringWithFormat:#"S%1dR%1d",indexPath.section,indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
Hope that should eliminate your problem of images getting displayed on one another,thanks :)