Swift-изображения изменяются на неправильные изображения при прокрутке после загрузки асинхронного изображения в UITableViewCell
Я пытаюсь асинхронно загружать изображения внутри моей ячейки 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
}
6 ответов
это потому, что UITableView повторно использует ячейки. Загрузка их таким образом заставляет асинхронные запросы возвращаться в разное время и испортить порядок.
Я предлагаю вам использовать некоторую библиотеку, которая облегчит вашу жизнь, как Kingfisher. Он будет загружать и кэшировать изображения для вас. Также вам не придется беспокоиться о асинхронных вызовах.
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
}
на cellForRowAtIndexPath:
1) присвоить значение индекса пользовательской ячейке. Например,
cell.tag = indexPath.row
2) в главном потоке перед назначением изображения проверьте, принадлежит ли изображение соответствующей ячейке, сопоставив ее с тегом.
dispatch_async(dispatch_get_main_queue(), ^{
if(cell.tag == indexPath.row) {
UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
thumbnailImageView.image = tmpImage;
}});
});
обновление
приведенный ниже код работал, но позже я переключился на использование Зимородок что удивительно просто.
ОБНОВЛЕНИЯ
Я не хотел использовать стороннюю библиотеку, такую как Alamofire и cell.тэг не работал на меня. Установка начального изображения на ноль сделала трюк для меня. Я также кэшировал изображения. Вот расширение, которое я написал для 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()
}
}
Вы можете вызвать этот метод как так в cellForRowAt
cell.postImage.downloadImage(from: self.posts[indexPath.row].pathToImage)
лучшее решение для этой проблемы у меня есть для Swift 3 или Swift 4 Просто напишите эти две строки
cell.videoImage.image = nil
cell.thumbnailimage.setImageWith(imageurl!)
В зависимости от реализации может быть много вещей, которые заставят все ответы здесь не работать (включая мой). Проверка тега не сработала для меня, проверка кэша тоже, у меня есть пользовательский класс фотографий, который несет полное изображение, миниатюру и больше данных, поэтому я должен позаботиться об этом тоже, а не просто предотвратить повторное использование изображения неправильно. Поскольку вы, вероятно, будете назначать изображения в ячейку imageView после их загрузки, вам понадобится к отменить загрузку и сбросить все, что вам нужно на prepareForReuse ()
пример, если вы используете что-то вроде SDWebImage
override func prepareForReuse() {
super.prepareForReuse()
self.imageView.sd_cancelCurrentImageLoad()
self.imageView = nil
//Stop or reset anything else that is needed here
}
Если вы подклассы imageview и обрабатывать загрузку самостоятельно убедитесь, что вы настроили способ отменить загрузку до завершения вызывается и вызовите отмена на prepareForReuse()
например
imageView.cancelDownload()
вы можете отменить это из UIViewController тоже. Это на себе или в сочетании с некоторые из ответов, скорее всего, решат эту проблему.
Swift 3
DispatchQueue.main.async(execute: {() -> Void in
if cell.tag == indexPath.row {
var tmpImage = UIImage(data: imgData)
thumbnailImageView.image = tmpImage
}
})