I'm working to get a solution since 15 days. Searched everywhere but didn't get rid of this problem. I have feeds to show in my UITableView. And I'm caching images. I'm loading images from firebase. The single feed contains more than one images. So I'm using Horizontal scrollview
and adding images to it.
I'm also using pagination for fetching feeds from firestore. The problem is when I scroll to bottom of tableView, the cell first shows previous feed's images then showing correct images. That's why I'm clearing contents of my cell's ScrollView, but still same problem persists.
HomeViewController Code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let feed = feeds[indexPath.row]
let user = feed.user
if let cell = tableView.dequeueReusableCell(withIdentifier: "FeedCell", for: indexPath) as? FeedCell {
if let images: [UIImage] = HomeViewController.postImagesCache.object(forKey: feed.postID as NSString) as? [UIImage] {
if let profileImage = HomeViewController.profileImageCache.object(forKey: user.profileImageUrl as NSString) {
cell.configCell(feed: feed, images: images, profileImage: profileImage, indexPath: indexPath)
} else {
cell.configCell(feed: feed, images: images, indexPath: indexPath)
}
} else {
tableView.isScrollEnabled = false
if let profileImage = HomeViewController.profileImageCache.object(forKey: user.profileImageUrl as NSString) {
cell.configCell(feed: feed, profileImage: profileImage, indexPath: indexPath)
} else {
cell.configCell(feed: feed, indexPath: indexPath)
}
tableView.isScrollEnabled = true
}
return cell
} else {
return FeedCell()
}
}FeedCell code
override func prepareForReuse() {
postMediaView.subviews.forEach{
if $0.restorationIdentifier != "imagesPageControl" {
print("removing subviews")
$0.removeFromSuperview()
}
}
self.profileImageView.image = nil
}
override func awakeFromNib() {
super.awakeFromNib()
profileImageView.layer.cornerRadius = profileImageView.frame.width/2
profileImageView.clipsToBounds = true
}
func configCell(feed: Feed, images: [UIImage]? = nil, profileImage: UIImage? = nil, indexPath: IndexPath) {
self.feed = feed
self.postCaptionLabel.text = feed.item.caption
self.fullnameLabel.text = feed.user.fullname
if images != nil {
self.createImageSlides(images: images!)
} else {
DispatchQueue.global(qos: .userInitiated).async {
var postImages: [UIImage] = []
let urls = feed.item.postImageUrls
let downloadGroup = DispatchGroup()
for i in 0 ..< urls.count {
downloadGroup.enter()
if urls[i] != "" {
let storageRef = Storage.storage().reference(forURL: urls[i])
storageRef.getData(maxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error != nil {
print("Error fetching post images: \(error!)")
downloadGroup.leave()
} else {
if let imgData = data {
if let img = UIImage(data: imgData) {
postImages.append(img)
downloadGroup.leave()
}
}
}
})
}
}
downloadGroup.wait()
DispatchQueue.main.async {
self.createImageSlides(images: postImages)
HomeViewController.postImagesCache.setObject(postImages as AnyObject, forKey: feed.postID as NSString)
}
}
}
if profileImage != nil {
self.profileImageView.image = profileImage
} else {
if feed.user.profileImageUrl != "" {
let storageRef = Storage.storage().reference(forURL: feed.user.profileImageUrl)
storageRef.getData(maxSize: 1 * 200 * 200) { (data, error) in
if error != nil {
print("Error fetching profile image: \(error!)")
} else {
if let imgData = data {
if let img = UIImage(data: imgData) {
self.profileImageView.image = img
self.feed.user.profileImage = img
HomeViewController.profileImageCache.setObject(img as UIImage, forKey: feed.user.profileImageUrl as NSString)
}
} else {
self.profileImageView.image = UIImage(named: "profile_icon")
}
}
}
} else {
self.profileImageView.image = UIImage(named: "profile_icon")
}
}
}
func createImageSlides(images: [UIImage]) {
if images.count < 2 {
let imageView = UIImageView(image: images[0])
imageView.contentMode = .scaleAspectFit
imageView.frame = CGRect(x: 0, y: 0, width: self.postMediaView.frame.width, height: self.postMediaView.frame.height)
imageView.roundCornersForAspectFit(radius: 10)
postMediaView.addSubview(imageView)
pageControl.numberOfPages = 1
} else {
let imageScrollView = UIScrollView()
for i in 0 ..< images.count {
let imageView = UIImageView(image: images[i])
imageView.contentMode = .scaleAspectFit
imageView.frame = CGRect(x: postMediaView.frame.width * CGFloat(i), y: 0, width: postMediaView.frame.width, height: postMediaView.frame.height)
imageView.roundCornersForAspectFit(radius: 10)
imageScrollView.addSubview(imageView)
}
imageScrollView.contentSize = CGSize(width: postMediaView.frame.width * CGFloat(images.count), height: postMediaView.frame.height)
postMediaView.addSubview(imageScrollView)
imageScrollView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
imageScrollView.isPagingEnabled = true
pageControl.numberOfPages = images.count
pageControl.currentPage = 0
postMediaView.bringSubviewToFront(pageControl)
}
}
As you know UITableViewCells may be reused. Clearing the old content is one thing to do, but you have another when setting up the cell asynchronously.
The cell may have been alreadly reused when the async call is completed. You need to check if the cell is already reused or not.
Try changing your `configCell` method like this:
//### Add some properties to find reuse
var uniqueValueForPostImages: Int? = nil
var uniqueValueForProfileImage: Int? = nil
func configCell(feed: Feed, images: [UIImage]? = nil, profileImage: UIImage? = nil, indexPath: IndexPath) {
self.feed = feed
self.postCaptionLabel.text = feed.item.caption
self.fullnameLabel.text = feed.user.fullname
if images != nil {
self.createImageSlides(images: images!)
} else {
//### set practically unique value to cell's property
let uniqueValue = Int.random(in: 0...Int.max)
self.uniqueValueForPostImages = uniqueValue
DispatchQueue.global(qos: .userInitiated).async {
var postImages: [UIImage] = []
let urls = feed.item.postImageUrls
let downloadGroup = DispatchGroup()
for i in 0 ..< urls.count {
downloadGroup.enter()
if urls[i] != "" {
let storageRef = Storage.storage().reference(forURL: urls[i])
storageRef.getData(maxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error != nil {
print("Error fetching post images: \(error!)")
} else {
if let imgData = data {
if let img = UIImage(data: imgData) {
postImages.append(img)
}
}
}
downloadGroup.leave()
})
}
}
downloadGroup.notify(queue: .main) {
//### When uniqueValueForPostImages does not have the same value as uniqueValue assigned, the cell is reused
if self.uniqueValueForPostImages != uniqueValue {
return
}
self.createImageSlides(images: postImages)
HomeViewController.postImagesCache.setObject(postImages as AnyObject, forKey: feed.postID as NSString)
}
}
}
if profileImage != nil {
self.profileImageView.image = profileImage
} else {
if feed.user.profileImageUrl != "" {
//### set practically unique value to cell's property
let uniqueValue = Int.random(in: 0...Int.max)
self.uniqueValueForProfileImage = uniqueValue
let storageRef = Storage.storage().reference(forURL: feed.user.profileImageUrl)
storageRef.getData(maxSize: 1 * 200 * 200) { (data, error) in
//### When uniqueValueForProfileImage does not have the same value as uniqueValue assigned, the cell is reused
if self.uniqueValueForProfileImage != uniqueValue {
return
}
if error != nil {
print("Error fetching profile image: \(error!)")
} else {
if let imgData = data {
if let img = UIImage(data: imgData) {
self.profileImageView.image = img
self.feed.user.profileImage = img
HomeViewController.profileImageCache.setObject(img as UIImage, forKey: feed.user.profileImageUrl as NSString)
}
} else {
self.profileImageView.image = UIImage(named: "profile_icon")
}
}
}
} else {
self.profileImageView.image = UIImage(named: "profile_icon")
}
}
}