在将异步图像加载到UITableViewCell后滚动时,Swift图像会更改为错误的图像

Ben*_*Nov 11 asynchronous image uitableview ios swift

我正在尝试在FriendsTableView(UITableView)单元格中异步加载图片.图像加载正常,但是当我滚动表格时,图像会改变几次,错误的图像会分配给错误的单元格.

我已经尝试了我在StackOverflow中可以找到的所有方法,包括向raw添加一个标签,然后检查它但是没有用.我还要验证应该使用indexPath更新的单元格并检查单元格是否存在.所以我不知道为什么会这样.

这是我的代码:

    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
    }
Run Code Online (Sandbox Code Playgroud)

A.G*_*A.G 21

在cellForRowAtIndexPath上:

1)为自定义单元格指定索引值.例如,

cell.tag = indexPath.row
Run Code Online (Sandbox Code Playgroud)

2)在主线程上,在分配图像之前,通过将图像与标记匹配来检查图像是否属于相应的单元格.

dispatch_async(dispatch_get_main_queue(), ^{
   if(cell.tag == indexPath.row) {
     UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
     thumbnailImageView.image = tmpImage;
   }});
});
Run Code Online (Sandbox Code Playgroud)


Ste*_*tic 8

这是因为UITableView重用了单元格.以这种方式加载它们会导致异步请求在不同的时间返回并弄乱订单.

我建议你使用一些图书馆,让你的生活更轻松如翠鸟.它将为您下载和缓存图像.您也不必担心异步调用.

https://github.com/onevcat/Kingfisher

你的代码看起来像这样:

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
    }
Run Code Online (Sandbox Code Playgroud)

  • 王鱼加载图像。但滚动时多次使用相同的图像。任何解决方案? (2认同)

Doe*_*ata 8

更新

有一些很棒的用于图像缓存的开源库,例如KingFisherSDWebImage。我建议您尝试其中之一,而不是编写自己的实现。

结束更新

因此,您需要做几件事才能使其发挥作用。首先让我们看一下缓存代码。

// Global variable or stored in a singleton / top level object (Ex: AppCoordinator, AppDelegate)
let imageCache = NSCache<NSString, UIImage>()

extension UIImageView {

    func downloadImage(from imgURL: String) -> URLSessionDataTask? {
        guard let url = URL(string: imgURL) else { return nil }

        // 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 nil
        }

        // download the image asynchronously
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let err = error {
                print(err)
                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()
        return task
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以像这样在 TableView 或 CollectionView 单元格之外使用它

let imageView = UIImageView()
let imageTask = imageView.downloadImage(from: "https://unsplash.com/photos/cssvEZacHvQ")
Run Code Online (Sandbox Code Playgroud)

要在 TableView 或 CollectionView 单元格中使用它,您需要将图像重置为 nil inprepareForReuse并取消下载任务。(感谢您指出@rob

final class ImageCell: UICollectionViewCell {

    @IBOutlet weak var imageView: UIImageView!
    private var task: URLSessionDataTask?

    override func prepareForReuse() {
        super.prepareForReuse()

        task?.cancel()
        task = nil
        imageView.image = nil
    }

    // Called in cellForRowAt / cellForItemAt
    func configureWith(urlString: String) {
        if task == nil {
            // Ignore calls when reloading
            task = imageView.downloadImage(from: urlString)
        }
    }
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as! ImageCell
    cell.configureWith(urlString: "https://unsplash.com/photos/cssvEZacHvQ") // Url for indexPath
    return cell
}
Run Code Online (Sandbox Code Playgroud)

请记住,即使您使用 3rd 方库,您仍然希望清除图像并取消任务 prepareForReuse

  • @PankajBhardwaj - 这是因为如果在异步图像检索完成时单元已被重用,DoesData 不会取消先前的图像请求。 (2认同)