tableView is duplicating entries after a button is tapped to favorite a photo?

I am building an App that fetches photos via nasa api and displays them in a collectionView. I am also trying to allow the user to favorite any photo they want by clicking a favorite button. Every time I click the favorites button to favorite a photo, all the ones that I have favorited prior keeps getting reinserted into the tableView over and over and that’s the problem. The tableView keeps inserting the same photo and its metadata more than . This is really bizarre because I made sure the data model for the favorites viewcontroller isn’t duplicated. I checked the array that holds the data model and there aren’t any duplicates. In the FavoritesViewController(), I made sure that the photo object being passed isn’t inserted into the data model array if it’s already in there by comparing the their URLS, and this seems to be working. I read many similar questions on here but I couldn’t find any similar to mine.

the three viewcontrollers involved are as follows:

  1. HomeViewController(): All this vc does is show the latest picture from the api and its metadata and allow the user to click a favorites button to add it in favoritesViewController() using a tableView.

2: FavoritesViewController(): This one displays the photos that the user favorited in a tableView

3: PicturesOftheDayViewController(): This one displays all the photos in a grid using collectionViews.

For the life of me, I can’t figure out why the tableView would duplicate entries when that viewcontroller has access to one photo at a time. I read many questions here where responders were saying it might the way the tableView is dequeing its cell but none of their solutions worked for me.

Will post the code below. It’s rather long but keep a close eye on the favsButtonTapped() method, and the favoritesViewController() as well as the tableDataSource object. Or maybe I am breaking something when I switch between the viewController using the tabBarController’s selectedIndex?

HomeViewController()

import UIKit
import JGProgressHUD
class HomeViewController: UIViewController {
    let userDefaults = UserDefaults.standard
    var isDataBeingPassed = false
    
    var photo: Photos!
    {
        didSet {
            updateImageView(for: photo)

        }
    }
    var photos = [Photos]()
    var store: PhotoStore!
    
    let scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()
    
   private let contentView: UIView = {
        let contentView = UIView()
        contentView.translatesAutoresizingMaskIntoConstraints = false
        return contentView
    }()
    
    
    
    var imageView: UIImageView = {
        let imageview = UIImageView()
        imageview.translatesAutoresizingMaskIntoConstraints = false
        imageview.contentMode = .scaleAspectFill
        imageview.layer.masksToBounds = true
        return imageview
    }()

    private var titleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 0
        label.font = .systemFont(ofSize: 24, weight: .semibold)
        label.backgroundColor = .tertiarySystemFill
        return label
    }()
    
    private var descriptionTextView: UITextView = {
        let textview = UITextView()
        textview.translatesAutoresizingMaskIntoConstraints = false
        textview.font = .systemFont(ofSize: 21)
        textview.textAlignment = .justified
        textview.textColor = .white
        textview.isEditable = false
        textview.backgroundColor = .black
        return textview
    }()

    override func loadView() {
        super.loadView()
    
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        navigationController?.navigationBar.prefersLargeTitles = true
        
        addSubviews()
        showHUD()
        constraints()
        
        //MARK: - navigation buttons
        navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "calendar"),
                                                           style: .plain,
                                                           target: self,
                                                           action: #selector(dateButtonTapped(_:)))
        
        navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "star"),
                                                           style: .plain,
                                                           target: self,
                                                           action: #selector(favsButtonTapped(_:)))
        
        
     
        
        navigationItem.leftBarButtonItem?.tintColor = .systemRed
        navigationItem.rightBarButtonItem?.tintColor = .systemRed

      
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if !isDataBeingPassed {
            fetchPhotoFromAPI()
        }
    }
    

    func fetchPhotoFromAPI() {
        
            
            store.fetchPhotosFromNasa {[weak self] photoResult in
                switch photoResult {
                case let .success(photos):
                    self?.photos = photos
                    if let lastPhoto = photos.last {
                        print(lastPhoto.date)
//                        self?.updateImageView(for: lastPhoto)
                        self?.photo = lastPhoto
                        
                        
                    }
                case let .failure(error):
                    print("error fetching interesting photos (error)")
                }
                
            }
        
    }

    
    func updateImageView(for photo: Photos) {
     
        //first convert the string date to Date() object 
        guard let convertedDate = DateFormatters.shared.inputDateFormatter.date(from: photo.date) else {
            return
        }
        let formattedDate = DateFormatters.shared.outPutDateFormatter.string(from: convertedDate)
        store.fetchImage(for: photo){[weak self] (imageResult) in
            switch imageResult {
            case let .success(image):
                self?.imageView.image = image
                self?.navigationItem.title = formattedDate
                
                self?.titleLabel.text = photo.title
                self?.descriptionTextView.text = photo.explanation
                
            case let .failure(error):
                print("Error downloading image: (error)")
            }
            
        }
        
    }
    
    func showHUD() {
        let hud = JGProgressHUD()
        hud.textLabel.text = "Loading"
        hud.show(in: view)
        
        hud.dismiss(afterDelay: 3.2)
    }
    
    @objc func dateButtonTapped(_ sender: UIBarButtonItem) {
        //configure and present data picker
      
    }
    

    
    @objc func favsButtonTapped(_ sender: UIBarButtonItem) {
  
        if let navVC = self.tabBarController?.viewControllers?[2] as? UINavigationController {
            
            let favoritesVC = navVC.topViewController as! FavoritesViewController
            
                favoritesVC.photo = self.photo
                favoritesVC.store = self.store
    
           
        }

    }
}

//MARK: - configure views and constraints
extension HomeViewController {
    
    func constraints() {
        let heightConstraint = contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
        heightConstraint.priority = UILayoutPriority(250)
        
        NSLayoutConstraint.activate([
            
            
            scrollView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            
            
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            heightConstraint,
            
            //
            imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
            imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            imageView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.3),
            
            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -40),
            titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 10),
            titleLabel.heightAnchor.constraint(equalToConstant: 70),
            
            
            descriptionTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            descriptionTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            descriptionTextView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
            descriptionTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
            
        ])
    }
    
    func addSubviews() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        contentView.addSubview(imageView)
        contentView.addSubview(titleLabel)
        contentView.addSubview(descriptionTextView)

    }
    
   
}

FavoritesViewController()

import UIKit
import JGProgressHUD

class FavoritesViewController: UIViewController, UITableViewDelegate {


    var store: PhotoStore! 
//    {
//        didSet {
//            tableDatSource.store = store
//           
//        }
//    }
    
    var photo: Photos!
//    {
//        didSet {
//            tableDatSource.photos.append(photo)
//            tableDatSource.photo = photo
//        }
//    }
    
   
    var tableView: UITableView?
    
    let tableDatSource = TableDataSource()
    
    let tableview: UITableView = {
        let table = UITableView(frame: .zero, style: .grouped)
//        table.translatesAutoresizingMaskIntoConstraints = false
        
        table.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier)
        return table
    }()
    
    //MARK: - lifecycle
    override func loadView() {
        super.loadView()
       setupTableView()

    }
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Favorites"
        view.backgroundColor = .systemBackground
        

    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if let store = store, let photo = photo {
            if !tableDatSource.photos.contains(where: {$0.url == photo.url}) {
                tableDatSource.photos.append(photo)
                tableDatSource.store = store
                tableview.reloadData()
            }


        }
       
       
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        print("the section titles is (tableDatSource.sectionTitles.count)")
    }
    
   
   
    //MARK: - Table
    func setupTableView() {
        view.addSubview(tableview)
        tableview.dataSource = tableDatSource
        tableview.delegate = self
        tableview.translatesAutoresizingMaskIntoConstraints = false
        self.tableView = tableview
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        self.tableView?.frame = view.bounds
    }
    
   
 
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 95.0
    }
   
   
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        
       
    }
    

   
}




PicturesOftheDayViewController()

import UIKit

class PictureOfTheDayViewController: UIViewController, UICollectionViewDelegate {
    var completion: ((Photos) -> Void)?
    var photodatasource = PhotoDataSource()
    var tabledatasource = TableDataSource()
    var collectionView: UICollectionView?
    
    var store: PhotoStore!
    
    let dateFormatter: DateFormatter = {
        let df = DateFormatter()
        df.dateStyle = .medium
        df.timeZone = .current
        return df
    }()
    
    
    //MARK: - lifecycle
    override func loadView() {
        super.loadView()
        view.backgroundColor = .secondarySystemBackground
        navigationItem.title = dateFormatter.string(from: Date())
    }
    
    override func viewDidLoad() {
        view.backgroundColor = .systemBackground
        
        super.viewDidLoad()
        configureCollectionView()
        //network stuff
        store.fetchPhotosFromNasa {[weak self](photoResult) in
            switch photoResult {
            case let .success(photo):
                print("successfully FOUND AND FOUND: (photo.count)")
                
                self?.photodatasource.photos = photo
//                self?.tabledatasource.photos = photo
                
            case let .failure(error):
                print("error fetching images from nasa: (error)")
                self?.photodatasource.photos.removeAll()
            }
            self?.collectionView?.reloadSections(IndexSet(integer: 0))
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
       
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        collectionView?.frame = view.bounds
    }
    

    //MARK: - collectionview
    func configureCollectionView() {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .vertical
        layout.minimumLineSpacing = 2
        layout.minimumInteritemSpacing = 0
        layout.itemSize = CGSize(width: view.frame.size.width/4, height: view.frame.size.height/7)
        
        let collectionview = UICollectionView(frame: .zero, collectionViewLayout: layout)
        
        
        view.addSubview(collectionview)
        collectionview.delegate = self
        collectionview.dataSource = photodatasource
        collectionview.translatesAutoresizingMaskIntoConstraints = false
        collectionview.register(PhotoCollectionViewCell.self, forCellWithReuseIdentifier: PhotoCollectionViewCell.identifier)
        self.collectionView = collectionview
        
    }
    
    
    
    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        let photo = photodatasource.photos[indexPath.row]
        
      
        //download the image data which could take sometime
        store.fetchImage(for: photo) {[weak self] (result) -> Void in
            
            //the index path for the photo might have changed between the time the request started and finishes, so find the most recent indexPath
            guard let photoIndex = self?.photodatasource.photos.firstIndex(of: photo), case let .success(image) = result else {
                return
            }
            
            let photoIndexPath = IndexPath(item: photoIndex, section: 0)
            //when the request finishes, find the current cell for this photo
            if let cell = self?.collectionView?.cellForItem(at: photoIndexPath) as? PhotoCollectionViewCell {
                cell.update(displaying: image)
            }
            
        }
        
      
        
    }
    

    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
      
        if let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first {
            let photo = photodatasource.photos[selectedIndexPath.row]
          
            if let navVC = self.tabBarController?.viewControllers?[0] as? UINavigationController {
                
                let homeVC = navVC.topViewController as! HomeViewController
                homeVC.isDataBeingPassed = true
                if homeVC.photo.url != photo.url{
                    homeVC.photo = photo
                }
                    
                    homeVC.store = self.store
                tabBarController?.selectedIndex = 0
        
               
            }
        }
    }
    
  
}
import UIKit

class TableDataSource: NSObject, UITableViewDataSource {
    var store: PhotoStore!

    var photos: [Photos] = [] {
        didSet {
            for photo in photos {
            
                if sections[photo.date] == nil {
                    sections[photo.date] = []
                }
                sections[photo.date]?.append(photo)
            }
        }
    }
    
    
    var sections: [String: [Photos]] = [:]
    
    var sectionTitles: [String] {
        return sections.keys.sorted()
    }
    
  
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return sectionTitles.count
    }
     
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let date = sectionTitles[section]
        return sections[date]?.count ?? 0
     
    }
    
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell

//        let photo = photos[indexPath.row]
        let date = sectionTitles[indexPath.section]
        guard let photo = sections[date]?[indexPath.row] else {
            fatalError()
        }
     

        cell.configure(with: photo)
//        guard let imageURL = photo.url else {
//            fatalError("Something is wrong with your URL")
//        }
        
        store.fetchImage(for: photo) {(result) in
            switch result {
                
            case let .success(image):
                if tableView.indexPath(for: cell) == indexPath {
                    cell.CellimageView.image = image
                }
            case let .failure(error):
                print("error getting image for tabledata source(error)")
            }
            
        }
           return cell
            
        }
        
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        
        return sectionTitles[section]
    }
    
    
    
    
    
    
}

Rather long but I’d appreciate any help.

I tried to make sure that the data model array isn’t duplicated.
I tried sending one photo object at a time.
I set the all the subviews inside the tableView to nil in prepareForReuse() method.

New contributor

Ahmed Abdi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật