UITableView showing wrong images of feedCell Swift

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)
            
        }
        
    }
Answered by OOPer in 368713022

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")
            }
            
        }
        
    }

It is probably a question of sequencing of the different calls in different threads.


Could you add print(#function, imageinfo) message each time you deal with image in cell, to see in which order those calls occur ?

Accepted Answer

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")
            }
            
        }
        
    }

Ohh ! I used same way you asked. It did sovle my problem.

Yes Claude31, you are correct. It was beacause of different threads.

UITableView showing wrong images of feedCell Swift
 
 
Q