Swift Images change to wrong images while scrollin

2019-02-05 01:44发布

I'm trying to async load pictures inside my FriendsTableView (UITableView) cell. The images load fine but when I'll scroll the table the images will change a few times and wrong images are getting assigned to wrong cells.

I've tried all methods I could find in StackOverflow including adding a tag to the raw and then checking it but that didn't work. I'm also verifying the cell that should update with indexPath and check if the cell exists. So I have no idea why this is happening.

Here is my code:

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("friendCell", forIndexPath: indexPath) as! FriendTableViewCell
        var avatar_url: NSURL
        let friend = sortedFriends[indexPath.row]

        //Style the cell image to be round
        cell.friendAvatar.layer.cornerRadius = 36
        cell.friendAvatar.layer.masksToBounds = true

        //Load friend photo asyncronisly
        avatar_url = NSURL(string: String(friend["friend_photo_url"]))!
        if avatar_url != "" {
                getDataFromUrl(avatar_url) { (data, response, error)  in
                    dispatch_async(dispatch_get_main_queue()) { () -> Void in
                        guard let data = data where error == nil else { return }
                        let thisCell = tableView.cellForRowAtIndexPath(indexPath)
                        if (thisCell) != nil {
                            let updateCell =  thisCell as! FriendTableViewCell
                            updateCell.friendAvatar.image = UIImage(data: data)
                        }
                    }
                }
        }
        cell.friendNameLabel.text = friend["friend_name"].string
        cell.friendHealthPoints.text = String(friend["friend_health_points"])
        return cell
    }

7条回答
你好瞎i
2楼-- · 2019-02-05 01:58

Depending on the implementation there can be many things that will cause all of the answers here to not work (including mine). Checking the tag did not work for me, checking the cache neither, i have a custom Photo class that carries the full image, thumbnail and more data, so i have to take care of that too and not just prevent the image from being reused improperly. Since you will probably be assigning the images to the cell imageView after they're done downloading, you will need to cancel the download and reset anything you need on prepareForReuse()

Example if you're using something like SDWebImage

  override func prepareForReuse() {
   super.prepareForReuse() 

   self.imageView.sd_cancelCurrentImageLoad()
   self.imageView = nil 
   //Stop or reset anything else that is needed here 

}

If you have subclassed the imageview and handle the download yourself make sure you setup a way to cancel the download before the completion is called and call the cancel on prepareForReuse()

e.g.

imageView.cancelDownload()

You can cancel this from the UIViewController too. This on itself or combined with some of the answers will most likely solve this issue.

查看更多
聊天终结者
3楼-- · 2019-02-05 02:01

On cellForRowAtIndexPath:

1) Assign an index value to your custom cell. For instance,

cell.tag = indexPath.row

2) On main thread, before assigning the image, check if the image belongs the corresponding cell by matching it with the tag.

dispatch_async(dispatch_get_main_queue(), ^{
   if(cell.tag == indexPath.row) {
     UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
     thumbnailImageView.image = tmpImage;
   }});
});
查看更多
放我归山
4楼-- · 2019-02-05 02:09

I solve the problem just implementing a custom UIImage class and I did a String condition as the code below:

let imageCache = NSCache<NSString, UIImage>()

class CustomImageView: UIImageView {
    var imageUrlString: String?

    func downloadImageFrom(withUrl urlString : String) {
        imageUrlString = urlString

        let url = URL(string: urlString)
        self.image = nil

        if let cachedImage = imageCache.object(forKey: urlString as NSString) {
            self.image = cachedImage
            return
        }

        URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
            if error != nil {
                print(error!)
                return
            }

            DispatchQueue.main.async {
                if let image = UIImage(data: data!) {
                    imageCache.setObject(image, forKey: NSString(string: urlString))
                    if self.imageUrlString == urlString {
                        self.image = image
                    }
                }
            }
        }).resume()
    }
}

It works for me.

查看更多
Root(大扎)
5楼-- · 2019-02-05 02:16

This is because UITableView reuses cells. Loading them in this way causes the async requests to return at different time and mess up the order.

I suggest that you use some library which would make your life easier like Kingfisher. It will download and cache images for you. Also you wouldn't have to worry about async calls.

https://github.com/onevcat/Kingfisher

Your code with it would look something like this:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("friendCell", forIndexPath: indexPath) as! FriendTableViewCell
        var avatar_url: NSURL
        let friend = sortedFriends[indexPath.row]

        //Style the cell image to be round
        cell.friendAvatar.layer.cornerRadius = 36
        cell.friendAvatar.layer.masksToBounds = true

        //Load friend photo asyncronisly
        avatar_url = NSURL(string: String(friend["friend_photo_url"]))!
        if avatar_url != "" {
            cell.friendAvatar.kf_setImageWithURL(avatar_url)
        }
        cell.friendNameLabel.text = friend["friend_name"].string
        cell.friendHealthPoints.text = String(friend["friend_health_points"])
        return cell
    }
查看更多
我只想做你的唯一
6楼-- · 2019-02-05 02:16

the Best Solution for This Problem i have is for Swift 3 or Swift 4 Simply write these two lines

  cell.videoImage.image = nil

 cell.thumbnailimage.setImageWith(imageurl!)
查看更多
Emotional °昔
7楼-- · 2019-02-05 02:18

UPDATE

The below code did work, but I later switched to using KingFisher which is remarkably simple.

END UPDATE

I didn't want to use a third party library such as Alamofire and cell.tag didn't work for me. Setting the initial image to nil did the trick for me. I also cached the images. Here is the extension I wrote for UIImageView:

let imageCache = NSCache<NSString, UIImage>()

extension UIImageView {

    func downloadImage(from imgURL: String!) {
        let url = URLRequest(url: URL(string: imgURL)!)

        // set initial image to nil so it doesn't use the image from a reused cell
        image = nil

        // check if the image is already in the cache
        if let imageToCache = imageCache.object(forKey: imgURL! as NSString) {
            self.image = imageToCache
            return
        }

        // download the image asynchronously
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error != nil {
                print(error!)
                return
            }

            DispatchQueue.main.async {
                // create UIImage
                let imageToCache = UIImage(data: data!)
                // add image to cache
                imageCache.setObject(imageToCache!, forKey: imgURL! as NSString)
                self.image = imageToCache
            }
        }
        task.resume()
    }
}

You can call this method like so in cellForRowAt

cell.postImage.downloadImage(from: self.posts[indexPath.row].pathToImage)

Source1 Source2

查看更多
登录 后发表回答