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
}
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
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.
You can cancel this from the UIViewController too. This on itself or combined with some of the answers will most likely solve this issue.
On cellForRowAtIndexPath:
1) Assign an index value to your custom cell. For instance,
2) On main thread, before assigning the image, check if the image belongs the corresponding cell by matching it with the tag.
I solve the problem just implementing a custom
UIImage
class and I did aString
condition as the code below:It works for me.
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:
the Best Solution for This Problem i have is for Swift 3 or Swift 4 Simply write these two lines
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:
You can call this method like so in cellForRowAt
Source1 Source2