0

I'm trying to create a feed just like the one in facebook. The problem is, the image on the succeeding rows will load the images from the initial rows and then correctly load their corresponding load. When you go to the top rows, the images previously loaded are gone. I've tried lazy loading but the problem persists. You could view the video to understand the problem better. (https://www.youtube.com/watch?v=NbgYM-1xYN4)

The images are asynchronously loaded and are fetched from our server.

Here are some Code:

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{

    return [latestPosts count];
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     NSDictionary * dataDict = [latestPosts objectAtIndex:indexPath.row];

    CardCell *cell = [self.feedTable dequeueReusableCellWithIdentifier:@"CardCell"];
    if (cell == nil) {
        cell = [[CardCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CardCell"];
    }
    [cell layoutSubviews];

    NSURL *imageURL = [[NSURL alloc] initWithString:[dataDict objectForKey:@"post_holder_image"]];
    NSURL *postImageURL = [[NSURL alloc] initWithString:[dataDict objectForKey:@"post_image"]];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
        NSData *postImageData = [NSData dataWithContentsOfURL:postImageURL];
        dispatch_async(dispatch_get_main_queue(), ^{
            cell.brandImage.image = [UIImage imageWithData:imageData];
            cell.postImage.image = [UIImage imageWithData:postImageData];
        });
    });

    cell.brandName.text = [dataDict objectForKey:@"post_holder"];
    cell.postDateTime.text = [dataDict objectForKey:@"post_datetime"];
    cell.postMessage.text = [dataDict objectForKey:@"post_content"];

    return cell;
}
Code cracker
  • 3,105
  • 6
  • 37
  • 67

3 Answers3

2

Use below method of UITableViewCell in your custom cell and set the image property to nil.

hope it will work for you.

-(void)prepareForReuse{
    [super prepareForReuse];

    // Then Reset here back to default values that you want.
}
prema janoti
  • 159
  • 1
  • 4
  • This is only a partial answer as it doesn't consider the possibility that one image download takes a long time and updates a cell when it shouldn't (after it has been reused)... – Wain Mar 12 '15 at 09:09
1

There are a few problems with the above.

As mentioned above you need to use a image as a placeholder (i.e blank white or an image of your choice) in the cell init code AND cell reuse.

You really need to cache your images, only download an image once and then store it in a dictionary. i.e.:

            UIImage *cachedImage = self.images[user[@"username"]];
            if (cachedImage) {
                //use cached image
                [button setBackgroundImage:cachedImage forState:UIControlStateNormal];
            }

            else {

                //download image and then add it to the dictionary }

where self.images is an NSMutableDictionary. You could also look into NSCache. If you don't cache the images you will find the table is very laggy when scrolling for a large number of rows because of the image conversion from data.

However this will not completely fix the problem if you start loading a table and scroll up and down very fast the images will appear to be in the wrong places and move around until they are all loaded. Cell reuse will confuse where to put the image. Make sure you put [tableView reloadItemsAtIndexPaths:@[indexPath]]; in your download block i.e.:

NSURL *imageURL = [[NSURL alloc] initWithString:[dataDict objectForKey:@"post_holder_image"]];
    NSURL *postImageURL = [[NSURL alloc] initWithString:[dataDict objectForKey:@"post_image"]];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
        NSData *postImageData = [NSData dataWithContentsOfURL:postImageURL];
        dispatch_async(dispatch_get_main_queue(), ^{
            cell.brandImage.image = [UIImage imageWithData:imageData];
            cell.postImage.image = [UIImage imageWithData:postImageData];
            [tableView reloadItemsAtIndexPaths:@[indexPath]];
        });
    });
Kex
  • 8,023
  • 9
  • 56
  • 129
  • Pretty good, +1, but you don't need to call `reloadItemsAtIndexPaths:`, it's an inefficient way to check the cell content and results in unwanted UI artefacts. You just need to cover the cell having been reused in the dispatch to main to update the cell - does the image still belong to that cell or has the cell been reused... Capture the index path in the block and check whether there is a cell for it. – Wain Mar 12 '15 at 09:13
  • Ah got it, something like if([self.tableView cellForRowAtIndexPath:indexPath]) set cell image? (placed in the download block) – Kex Mar 12 '15 at 09:26
  • Yes, or if it returns a cell then update the image on that cell (then you don't need to capture the cell in the block, just the index path) – Wain Mar 12 '15 at 09:27
  • Thanks, I'll have a look at some of my older code and try implement this later. – Kex Mar 12 '15 at 09:32
0

You need to set imageview.image nil or you should set your placeholder image while reusing cells. Here is same question Async image loading from url inside a UITableView cell - image changes to wrong image while scrolling Other than that if you are not using parse.com api ect. you can check https://github.com/rs/SDWebImage or https://github.com/AFNetworking/AFNetworking There are tons of answer about this topic.

[cell layoutSubviews];
cell.brandImage.image = nil;
cell.postImage.image = nil;
Community
  • 1
  • 1
kocakmstf
  • 135
  • 12