Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Images in UITableViewCells are loading wrong

I'm working on an app where I wanna show images, nearly as big as screensize, in a UITableView with custom cells. The Images are loading good, a bit slow but they load, but when I scroll in the TableView they appear in the wrong Cell (for example Image1 in Cell5 where Image5 is supposed to appear) after 1 or 2 seconds the correct image appears. In Order not to have to many datatraffic I thought about a "cache"-like URL/Image storage like this:

- (id) init
{
    if (self = [super init]) {
        self.cacheStoreDictionary = [NSMutableDictionary new];
    }
    return  self;
}

- (void) startLoadingImageWithUrl:(NSString *)urlString forItem:(id)item
{
    if (self.cacheStoreDictionary[urlString]) {
        UIImage *image = self.cacheStoreDictionary[urlString];
        [self.delegate lcCachedImageLoader:self didLoadImage:image forItem:item wasCacheHit:YES];
    } else {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];
            UIImage *image = [UIImage imageWithData:data];
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.cacheStoreDictionary setObject:image forKey:urlString];
                [self.delegate lcCachedImageLoader:self didLoadImage:image forItem:item wasCacheHit:NO];
            });
        });
    }
}

The URL and the rest of the data I need I'm loading via AFNetwork like this:

- (void)fetchTicketEvents
{
    // Login URL
    NSURL *eventsURL = [NSURL URLWithString:TH_API_BASEURL];

    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:eventsURL];
    httpClient.parameterEncoding = AFJSONParameterEncoding;

    // Set request parameters
    NSDictionary *params = nil;
    NSMutableURLRequest *request = [httpClient requestWithMethod:@"GET"
                                                            path:TH_API_PATH_TICKET_EVENT_INDEX
                                                      parameters:params];
    request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;

    // Prepare request
    AFJSONRequestOperation *eventsRequest = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        if ([self.delegate respondsToSelector:@selector(thAPITicket:didLoadTicketEvents:response:errorCode:)]) {
            NSArray *jsonEventArray = JSON;
            NSMutableArray *event = [[NSMutableArray alloc] initWithCapacity:jsonEventArray.count];
            for (NSDictionary *eventDictionary in [JSON allObjects]) {
                [event addObject:[LCEvent eventFromDictionary:eventDictionary]];
            }
            [self.delegate thAPITicket:self
                             didLoadTicketEvents:YES
                                        response:@{@"response" : response, @"json" : JSON, @"event" : event}
                                       errorCode:TH_API_ERR_TICKETINDEX_NO_ERROR];
#if LOG_NETWORKING
            [self printJSON:JSON];
#endif
            //Network indicator off
            [LCSharedInstance hideNetworkActivity];

            //Allow Slide-To-Refresh
            [LCSharedInstance singletone].allowSlideToRefresh = YES;
            [LCSharedInstance singletone].isShownLoadingMessage = NO;
            [LCSharedInstance singletone].isLoading = NO;
        }
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        if ([self.delegate respondsToSelector:@selector(thAPITicket:didLoadTicketEvents:response:errorCode:)]) {
            if (nil == JSON) {
                JSON = @{};
            }
            [self.delegate thAPITicket:self
                             didLoadTicketEvents:NO
                                        response:@{@"response" : response, @"json" : JSON}
                                       errorCode:TH_API_ERR_TICKETINDEX_NO_ERROR];
            [LCSharedInstance hideNetworkActivity];

        }
        //Network indicator off
        [LCSharedInstance hideNetworkActivity];
    }];
    [LCSharedInstance showNetworkActivity];
    // Send request
    [eventsRequest start];
}

Here is how i fill the TableViewCell:

- (void) lcCachedImageLoader:(LCCachedImageLoader *)cachedImageLoader didLoadImage:(UIImage *)teaserImage forItem:(id)item wasCacheHit:(BOOL)wasCacheHit
{
    //Teaser Image Implementation
    UIImageView *imageView = ((UIImageView*)[((UIView*)item)viewWithTag:100]);
    imageView.alpha = 0.2;
    imageView.image = teaserImage;
    imageView.contentMode = UIViewContentModeScaleToFill;
    [UIView animateWithDuration:0.5 animations:^{
        imageView.alpha = 1.0;
    }];
}


#pragma mark - tableview delegation
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"offerCell";
    LCEvent *event = self.eventArray[indexPath.row];
    tableView = self.tableView;
    LCClusterViewController *clusterVC = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

#pragma mark - Cell Config and Style Attributes
    clusterVC.titleLabel.attributedText = LCAttrText(event.titleString, LCFontStyleEventName);
    clusterVC.ticketDetailsLabel.attributedText = LCAttrText(event.subtitleString, LCFontStyleEventTitle);
    clusterVC.oldPriceLabel.attributedText = LCAttrText(event.oldPriceString, LCFontStyleEventOldPrice);
    clusterVC.actualPriceLabel.attributedText = LCAttrText(event.actualPriceString, LCFontStyleEventPrice);
    clusterVC.timeLabel.attributedText = LCAttrText(event.timeString, LCFontStyleEventInfoText);
    clusterVC.ticketCountLabel.attributedText = LCAttrText(event.stockString, LCFontStyleEventInfoText);
    clusterVC.dateLabel.attributedText = LCAttrText(event.dateString, LCFontStyleEventDateText);
... and on and on

so thanks for any helpful hints

like image 786
d3p0nit Avatar asked May 22 '13 08:05

d3p0nit


1 Answers

The table view cells are being reused for performance reason, so they will retain the previously used image in the UIImageView if you do not set it to anything before you load the image.

Right before you call the startLoadingImageWithUrl, you can set the initial image of the imageView with a placeholder image first or maybe add loading indicator on top of the imageView. But remember to remove the loading indicator when the real image is loaded.

- (void) startLoadingImageWithUrl:(NSString *)urlString forItem:(id)item
{
    UIImageView *imageView = ((UIImageView*)[((UIView*)item)viewWithTag:100]);
    imageView.image = placeholderImage; 
    if (self.cacheStoreDictionary[urlString]) {
        UIImage *image = self.cacheStoreDictionary[urlString];
        [self.delegate lcCachedImageLoader:self didLoadImage:image forItem:item wasCacheHit:YES];
    } else {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];
            UIImage *image = [UIImage imageWithData:data];
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.cacheStoreDictionary setObject:image forKey:urlString];
                [self.delegate lcCachedImageLoader:self didLoadImage:image forItem:item wasCacheHit:NO];
            });
        });
    }
}

And for the image appearing at the wrong cell after the image is loaded, it is because you are passing item, which is reused cell, as a parameter in the startLoadingImage and didLoadImage. Try to pass indexPath instead and locate the correct cell with the indexPath, the update the cell image view.

UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

like image 60
Valent Richie Avatar answered Sep 19 '22 15:09

Valent Richie