Search code examples
swifttableviewcollectionviewreuseidentifierprepareforreuse

Collectionview in TableviewCell, data repeat


I've a collectionview inside my resizable tablviewCells. Tableview has one cell in each 'n' number of sections. Datasource and delegate of collectionview are set to the tableviewCell. There is an API called on tablview's cellForRowAt, and the result is rendered on the collectionview for each cell. After the result is fetched, a delegate tells the tableview that collectionview is loaded and it should reload that cell without calling the API this time. But the problem is that my collectionview data is repeated after every 2 tableviewCells.

I know prepareForReuse should be override to get rid of cell reuse problems. I've implemented prepareForReuse in my collectionviewCells and set my label.text and imageView.image to nil. However i'm not sure what to add to prepareForReuse for my tableviewCell.

// TableView class

override func viewDidLoad() {
        super.viewDidLoad()

        storiesSections = [....]

        tableView.register(UINib(nibName: "RWFeedTableViewCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
        tableView.estimatedRowHeight = 1
        tableView.rowHeight = UITableView.automaticDimension
    }

override func numberOfSections(in tableView: UITableView) -> Int {
        return storiesSections.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! RWFeedTableViewCell

        if cell == nil {
            cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "reuseIdentifier") as! RWFeedTableViewCell
        }

        cell.delegate = self
        cell.fetchData(feedSection: storiesSections[indexPath.section], indexPath: indexPath)
        return cell
    }

// delegate for tableview reload
func collectionViewDidEnd(updatedFeedSection: FeedSection, indexPath: IndexPath) {
        storiesSections[indexPath.section] = updatedFeedSection
        tableView.beginUpdates()
        tableView.endUpdates()
    }

// TableViewCell class


    override func awakeFromNib() {
        super.awakeFromNib()
        initializeCode()
    }

    func initializeCode() {
        // Set layout
        self.collectionView.collectionViewLayout = RWWaterfallLayout2()

        self.collectionView.register(UINib(nibName: "\(ImageThenTitleViewCell.self)", bundle: nil), forCellWithReuseIdentifier: kImageThenTitleCellID)
        self.collectionView.register(UINib(nibName: "\(LeftImageCell.self)", bundle: nil), forCellWithReuseIdentifier: kLeftImageCellID)
        self.collectionView.contentInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
        self.collectionView.isScrollEnabled = false
        self.collectionView.dataSource = self
        self.collectionView.delegate = self
    }

    func fetchData(feedSection: FeedSection, indexPath: IndexPath)  {

        if feedSection.isLoadComplete {
            return
        }

        if let catID = feedSection.categoryID {

            let urlString = URL(string: <urlString>)

            let urlRequest = URLRequest(url: urlString!)

            let config = URLSessionConfiguration.default
            let session = URLSession(configuration: config)

            let task = session.dataTask(with: urlRequest) { (data, response, error) in

                if error == nil {

                } else {
                    print(error?.localizedDescription as Any)
                }

                guard let responseData = data else {
                    print("Error: did not receive data")
                    return
                }

                do {
                    guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])as? [String: Any] else {
                        print("error trying to convert data to JSON")
                        return
                    }
                    print("success convert data to JSON")

                    DispatchQueue.main.async {

                        var updatedFeedSection = feedSection
                        updatedFeedSection.storiesArray? = (todo["data"]! as! Array)
                        updatedFeedSection.isLoadComplete = true

                        self.feedSection = updatedFeedSection

                        self.collectionView.reloadData()

                        self.collectionView.performBatchUpdates({

                        }, completion: { (complete) in
                            self.collectionViewHeightConstraint.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height + self.collectionView.contentInset.top + self.collectionView.contentInset.bottom
                            self.delegate?.collectionViewDidEnd(updatedFeedSection: updatedFeedSection, indexPath: indexPath)
                        })
                    }
                } catch  {
                    print("error trying to convert data to JSON")
                    return
                }

            }
            task.resume()
        }
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if self.feedSection == nil {
            return 0
        } else {
            return (self.feedSection?.storiesArray?.count)!
        }
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let indexForTen = indexPath.item%10

        let story = self.feedSection?.storiesArray?[indexPath.item]

        if indexForTen == 0 {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kImageThenTitleCellID, for: indexPath) as! ImageThenTitleViewCell
            cell.setupData(story: story!)
            return cell
        }
        else {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kLeftImageCellID, for: indexPath) as! LeftImageCell
            cell.setupData(story: story!)
            return cell
        }
    }

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

// Collectionview Cell


    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    func setupData(story: Dictionary<String, Any>){
        self.storyImage.image = nil // reset the image

        let thumbImage = story["image"] as! Dictionary<String, String>

        self.storyTitle.text = story["t"] as? String
        self.storyImage.downloaded(from: (thumbImage["m"])!)

        self.layer.borderColor = UIColor.lightGray.cgColor
        self.layer.borderWidth = 1
        self.layer.cornerRadius = 8
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        storyImage.image = nil
        storyTitle.text = nil
    }

// FeedSection struct


struct FeedSection {
    var categoryID: String?
    var storiesArray : [Dictionary<String, Any>]?
    var isLoadComplete: Bool

    init(categoryID: String) {
        self.categoryID =  categoryID
        self.storiesArray = []
        self.isLoadComplete = false
    }
}

Currently the 3rd tableviewCell repeats the data of 1st tablviewCell. How to avoid repeating cell data?


Solution

  • Only problem was the feedSection object in TableViewCell. It should be initialized at the time fetchData() is called. And just reload the collectionView if isLoadComplete is true.

    Also since isLoadComplete is set on completion handler of URLSession, I set it to true the time API is called. So the same api will not be called while waiting for response. Maybe an enum could be set for api call and api response events on FeedSection. But for now this works.

    func fetchData(feedSection: FeedSection, indexPath: IndexPath)  {
    
            self.feedSection = feedSection
    
            if self.feedSection.isLoadComplete {
                self.collectionView.reloadData()
                return
            }
    
            if let catID = feedSection.categoryID {
    
                let urlString = URL(string: <urlString>)
    
                let urlRequest = URLRequest(url: urlString!)
    
                let config = URLSessionConfiguration.default
                let session = URLSession(configuration: config)
    
                self.feedSection.isLoadComplete = true
                self.delegate?.collectionViewDidEnd(updatedFeedSection: self.feedSection, indexPath: indexPath)
    
                let task = session.dataTask(with: urlRequest) { (data, response, error) in
    
                    if error == nil {
    
                    } else {
                        print(error?.localizedDescription as Any)
                    }
    
                    guard let responseData = data else {
                        print("Error: did not receive data")
                        return
                    }
    
                    do {
                        guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])as? [String: Any] else {
                            print("error trying to convert data to JSON")
                            return
                        }
                        print("success convert data to JSON")
    
                        DispatchQueue.main.async {
    
                            self.feedSection.storiesArray? = (todo["data"]! as! Array)
                            self.feedSection.isLoadComplete = true
    
                            self.collectionView.reloadData()
    
                            self.collectionView.performBatchUpdates({
    
                            }, completion: { (complete) in
    
                                self.collectionViewHeightConstraint.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height + self.collectionView.contentInset.top + self.collectionView.contentInset.bottom
                                self.delegate?.collectionViewDidEnd(updatedFeedSection: self.feedSection, indexPath: indexPath)
                            })
                        }
                    } catch  {
                        print("error trying to convert data to JSON")
                        return
                    }
    
                }
                task.resume()
            }
        }