简体   繁体   中英

SDWebImage displaying wrong images in UITableView

In my iOS app, I'm displaying images inside multiple UITableViewCells . However, it's not displaying the correct images in each cell.

First I load some content from a Feedly stream with the method below:

- (void)loadStreams {
    NSString *feedName = [NSString stringWithFormat:@"%@-id", self.category];

    NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
    NSString *accessToken = [standardUserDefaults objectForKey:@"AccessToken"];
    NSString *feedId = [standardUserDefaults objectForKey:feedName];
    NSString *feedPartial = [feedId stringByReplacingOccurrencesOfString:@"/" withString:@"%2F"];
    NSString *feedUrl = [NSString stringWithFormat:@"https://sandbox.feedly.com/v3/streams/%@/contents", feedPartial];

    NSLog(@"The Feedly url is: %@", feedUrl);

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:feedUrl]];


    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    [mutableRequest addValue:accessToken forHTTPHeaderField:@"Authorization"];

    request = [mutableRequest copy];

    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.responseSerializer = [AFJSONResponseSerializer serializer];

    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {


        NSArray *jsonArray = (NSArray *)[responseObject objectForKey:@"items"];

        self.continuation = [responseObject objectForKey:@"continuation"];

        NSMutableArray *tempStreams = [[NSMutableArray alloc] init];

        for (NSDictionary *dic in jsonArray) {

            NSLog(@"Dic contains: %@", dic);

            NSDictionary *originArray = [dic objectForKey:@"origin"];
            NSDictionary *visualArray = [dic objectForKey:@"visual"];
            NSArray *alternateArray = [dic objectForKey:@"alternate"];

            NSDictionary *alternate = [alternateArray objectAtIndex:0];

            NSString *image = [visualArray objectForKey:@"url"];
            NSString *title = [dic objectForKey:@"title"];
            NSString *author = [dic objectForKey:@"author"];
            NSString *date = [dic objectForKey:@"published"];
            NSDictionary *contentum = [dic objectForKey:@"content"];
            NSString *content = [contentum objectForKey:@"content"];
            NSString *owner = [originArray objectForKey:@"title"];
            NSString *givenid = [dic objectForKey:@"id"];
            NSString *href = [alternate objectForKey:@"href"];

            NSDate *publisher = [NSDate dateWithTimeIntervalSince1970:([date doubleValue] / 1000.0)];
            NSString *published = publisher.timeAgoSinceNow;

            NSDictionary *data = [[NSDictionary alloc] initWithObjectsAndKeys:title, @"title", image, @"imageurl", published, @"published", owner, @"owner", content, @"content", givenid, @"givenid", href, @"href", author, @"author", nil];

            Stream *stream = [[Stream alloc] initWithDictionary:data];
            [tempStreams addObject:stream];
        }


        self.streams = [[NSMutableArray alloc] initWithArray:tempStreams];
        tempStreams = nil;

        [self.tableView reloadData];

    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {

        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Services"
                                                            message:[error localizedDescription]
                                                           delegate:nil
                                                  cancelButtonTitle:@"Ok"
                                                  otherButtonTitles:nil];
        [alertView show];
    }];

    [operation start];
}

This passes the data to an object called Stream , which consists of the code below:

Stream.h

#import <Foundation/Foundation.h>

@interface Stream : NSObject

@property (strong, nonatomic)NSString *name;
@property (strong, nonatomic)NSString *thumbnail;
@property (strong, nonatomic)NSString *photo;
@property (strong, nonatomic)NSString *published;
@property (strong, nonatomic)NSString *content;
@property (strong, nonatomic)NSString *givenid;
@property (strong, nonatomic)NSString *linky;
@property (strong, nonatomic)NSString *author;

- (id)initWithName:(NSString *)aName
         thumbnail:(NSString *)aThumbnail
             photo:(NSString *)aPhoto
         published:(NSString *)aPublished
           content:(NSString *)aContent
           givenid:(NSString *)aId
             linky:(NSString *)aLinky
            author:(NSString *)aAuthor;

- (id)initWithDictionary:(NSDictionary *)dic;

@end

Stream.m

#import "Stream.h"

@implementation Stream

//The designed initializer
- (id)initWithName:(NSString *)aName
         thumbnail:(NSString *)aThumbnail
             photo:(NSString *)aPhoto
         published:(NSString *)aPublished
           content:(NSString *)aContent
           givenid:(NSString *)aId
              linky:(NSString *)aLinky
             author:(NSString *)aAuthor{
    self = [super init];

    if (self) {
        self.name = aName;
        self.thumbnail = aThumbnail;
        self.photo = aPhoto;
        self.published = aPublished;
        self.content = aContent;
        self.givenid = aId;
        self.linky = aLinky;
        self.author = aAuthor;
    }

    return self;
}

- (id)initWithDictionary:(NSDictionary *)dic {
    self = [self initWithName:dic[@"title"] thumbnail:dic[@"imageurl"] photo:dic[@"imageurl"] published:dic[@"published"] content:dic[@"content"] givenid:dic[@"givenid"] linky:dic[@"href"] author:dic[@"author"]];
    return self;
}

- (id)init {
    self = [self initWithName:@"Undifined" thumbnail:@"Undifined" photo:@"Undifined" published:@"Undifined" content:@"Undifined" givenid:@"Undifined" linky:@"Undifined" author:@"Undifined"];
    return self;
}

@end

And in the end I build a cell like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString * reuseIdentifier = @"programmaticCell";
    MGSwipeTableCell * cell = [self.tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    if (!cell) {
        cell = [[MGSwipeTableCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
    }

    CGFloat brightness = [UIScreen mainScreen].brightness;

    cell.textLabel.text = [self.streams[indexPath.row] name];
    cell.detailTextLabel.text = [self.streams[indexPath.row] published];

    NSString *imageUrl = [NSString stringWithFormat: @"%@", [self.streams[indexPath.row] photo]];

    NSLog(@"Image is: %@ and path is: %d", imageUrl, indexPath.row);

    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:imageUrl]
                      placeholderImage:[UIImage imageNamed:@"tile-blue.png"] options:indexPath.row == 0 ? SDWebImageRefreshCached : 0];

    cell.delegate = self; //optional

    return cell;
}

What happens though, is that it displays the wrong image in a lot of cells and sometimes the same image for a couple of cells. What am I doing wrong here?

These are symptoms of cell reuse. There are two issues you will have to deal with.

(1) you should reset your cell's content before it is reused. To do this you can override prepareForReuse in the cell and nil out the relevant properties (such as cell.imageView ). If you don't do this, you will see the old image -after- the cell has been recycled, before SDWebImage has assigned a new image.

(2) as SDWebImage image retrieval is async, the image may arrive after the cell has scrolled off the screen (and recycled with new content. You need to check whether the image is still relevant before assigning it to the imageView. I am not sure if this is possible with the SDWebImage UIImageView category method. You may have to dissect SDWebImage a little . You can get more control over the process using the SDWebImageManager method:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

You could use it something like this (in CellForRowAtIndexPath )

[[SDWebImageManager defaultManager] downloadImageWithURL:url 
                                                options:0 
                                               progress:nil 
                                              completed:
^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
     if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
           //image is still valid for this cell
           cell.image = image;
      }
}];
  1. Push a unique id on the stack before your closure and check it when your closure completes
  2. prepareForReuse

Like this:

func updateArtistImage(url: URL) {
        let _eventId = self.event?.id
        SDWebImageManager.shared().loadImage(with: url, options: [], progress: nil) { (image, data, error, cacheType, finished, url) in
            if self.event!.id == _eventId {
                if error == nil {
                    self.artistImageView.image = image
                } else {
                    self.artistImageView.image = UIImage(named: "error_image")
                }
            }
        }
    }

and this:

override func prepareForReuse() {
    super.prepareForReuse()
    self.artistImageView.image = nil
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM