1

===EDIT===

Ok! Finally I think I've made it working. When I was reading this article (http://matteomanferdini.com/the-correct-way-to-display-lists-in-ios-and-what-many-developers-do-wrong/#reuse), I wanted to apply best practice so I made some refactoring and created a new class for my data source. By doing this, I think I've found why I was getting this weird issue. Now, for my TableView cells (AgendaTableViewCell), I've removed the events property, and made a day property instead. Here my newly class

//
//  AgendaTableViewCell.swift
//  fnfa
//
//  Created by JBARA Omar on 15/02/2018.
//  Copyright © 2018 JBARA Omar. All rights reserved.
//

import UIKit

class AgendaTableViewCell: UITableViewCell {

    @IBOutlet weak var eventsCollectionView: UICollectionView!

    var eventsDataSource: EventsDataSource?
    var day: Date? {
        didSet{
            setup()
        }
    }
    let dateFormatter = DateFormatter()

    private func setup() {
        self.eventsDataSource = EventsDataSource(events: DataMapper.instance.events.findBy(date: day!)!)
        print("setup row")
        print(day)
        print(self.eventsDataSource?.events)
        dateFormatter.setLocalizedDateFormatFromTemplate("d")
        eventsCollectionView.dataSource = eventsDataSource
        eventsCollectionView.reloadData()
        eventsCollectionView.register(UINib(nibName: "EventCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: String(describing: EventCollectionViewCell.self))
    }

}

I think I'm not getting outdated values the first time because now, every time a cell get reused, it set the day property, hence calling the setup method. Now I'm sure that it sets the data with the correct events for that particular day. I think that what was happenning before is that it was using the old events property from another reused cell. I don't know if it is a good way to achieve this (the fact that I set the data source every time a cell get reused don't seem viable, but since I'm dependant of that day property, I could maybe not do otherwise), but actually it's working!

===INITIAL QUESTION===

I don't know if it's a bug or me being too dumb, but recently this thing drove me crazy. Let me explain what's happenning.

First of all, I have a list of events in a json which I am loading and parsing. It works just all fine.

In a view, I'm displaying events for several days. It is made of a TableView. In this TableView, I've made xib files with custom class cell, and in my code I register them correctly (using tableView.register(UINib(nibName: "AgendaTableViewCell", bundle: nil), forCellReuseIdentifier: "dayRow"))

Then here's the tricky part. In my table view, I have a header for each day I'm displaying, thus defining a section for each, and in each sections, I have exactly one row.

In this row, I'm displaying a CollectionView, and I've also made a CollectionViewCell template in a xib file with a custom class. It's fetching data from my json correctly, but I have one very annoying problem.

Here's first a gif to help you understand what's happenning, and the code of my AgendaViewController (the main view which have the TableView), the AgendaTableViewCell, which is the cell in my TableView, and which is also responsible for managing my CollectionView in the cells, and an EventCollectionViewCell which represent a single cell for the collection view (it's this one which show information about the event).

show gif

The weird thing here is that, for the first two days (Mercredi 4 and Jeudi 5), it works as expected, but for Vendredi 6, it's getting really weird. The problem is not from the data I fetch, because I verified, my events: [Event]? in the AgendaTableViewCell and it have the correct events.

I think the problem is really on dequeuing / reusing cells. Because when I go to the second cell, then go back to the first, it's working as expected, and I no longer have the problem. I really don't know what to do, I've tried with prepareForReuse, but if I understood correctly, it's relevant when it's about visual content, like for resetting an image to prevent an image from another cell being reused. But here, it's really the text content which is wrong, and only this. I really don't understand what's happenning here. Also, what's really weird it's that it's working normally if I test with only 3 days on my days arrray. But when the array is longer than 3, then the bug is here. That's the odd part. And I noticed that it seems to follow a weird pattern. I tested with 15 days, and in my collections views, it looks like the first cell before it's reused is from the 4th day, the 5th day and the 8th day. And it goes on and on.

And here my files :

AgendaViewController

//
//  AgendaViewController.swift
//  fnfa
//
//  Created by JBARA Omar on 15/02/2018.
//  Copyright © 2018 JBARA Omar. All rights reserved.
//

import UIKit

class AgendaViewController: UITableViewController {


    private let reuseIdentifier = "dayRow"

    private var days = [
        Date(timeIntervalSince1970: 1522836000), // 4 Avril 2018, 12:00
        Date(timeIntervalSince1970: 1522922400), // 5 Avril 2018, 12:00
        Date(timeIntervalSince1970: 1523008800), // 6 Avril 2018, 12:00
        Date(timeIntervalSince1970: 1523095200), // 7 Avril 2018, 12:00
        Date(timeIntervalSince1970: 1523181600)  // 8 Avril 2018, 12:00
    ]
    private let dateFormatter = DateFormatter()
    private let locale = Locale(identifier: "fr_FR")

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.allowsSelection = false
        dateFormatter.locale = locale
        tableView.register(UINib(nibName: "AgendaTableViewCell", bundle: nil), forCellReuseIdentifier: reuseIdentifier)
        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let row = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! AgendaTableViewCell
        row.events = DataMapper.instance.events.findBy(date: days[indexPath.section])
        return row
    }


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

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

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let view = super.tableView(tableView, viewForHeaderInSection: section)
        view?.backgroundColor = #colorLiteral(red:0.1175380871, green:0.1734368503, blue:0.310670346, alpha:1)

        return view
    }

    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 50
    }

    override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
        if (view is UITableViewHeaderFooterView) {
            let header = view as! UITableViewHeaderFooterView
            dateFormatter.setLocalizedDateFormatFromTemplate("EEEE d")
            header.textLabel?.text = dateFormatter.string(from: days[section]).capitalized(with: locale)
            header.textLabel?.textAlignment = .center
            header.textLabel?.textColor = #colorLiteral(red:1.0, green:1.0, blue:1.0, alpha:1.0)
        }
    }

}

EventCollectionViewCell

//
//  XIBEventCollectionViewCell.swift
//  fnfa
//
//  Created by Jbara Omar on 18/02/2018.
//  Copyright © 2018 JBARA Omar. All rights reserved.
//

import UIKit

class EventCollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var eventImage: UIImageView!
    @IBOutlet weak var labelTitle: UILabel!

    var event: Event? {
        didSet{
            eventImage.image = event?.getUIImage()
            labelTitle.text = event?.name
        }
    }


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



}

AgendaTableViewCell

//
//  AgendaTableViewCell.swift
//  fnfa
//
//  Created by JBARA Omar on 15/02/2018.
//  Copyright © 2018 JBARA Omar. All rights reserved.
//

import UIKit

class AgendaTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {

    @IBOutlet weak var eventsCollectionView: UICollectionView!

    let dateFormatter = DateFormatter()
    var identifier = "events"
    var events: [Event]?

    override func awakeFromNib() {
        super.awakeFromNib()
        dateFormatter.setLocalizedDateFormatFromTemplate("d")
        eventsCollectionView.delegate = self as UICollectionViewDelegate
        eventsCollectionView.dataSource = self as UICollectionViewDataSource
        eventsCollectionView.register(UINib(nibName: "EventCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: identifier)
    }


    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return (events?.count)!
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = eventsCollectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! EventCollectionViewCell
        cell.event = events?[indexPath.item]
        return cell
    }


    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

DataMapper is just a class I've made to get my data from the json, it's not relevant here to put the code because it only use JSONDecoder internally.

I'm new to iOS development, so some help would be really appreciable! Thank you!

Omar Jbara
  • 11
  • 3
  • If I understood you correctly, I think I've already tested this by doing something like this : In my `tableView cellForRowAt`, I added after setting row.events, something like `row.eventsCollectionView.delegate = row as ...`, `row.eventsCollectionView.dataSource = row as ...`, and of course removed the code from the `awakeFromNib` method, to make sure that I was setting my dataSource and delegate after I've set `row.events`. But still the same problem :/ – Omar Jbara Feb 18 '18 at 19:34
  • I hope it was, but I've already read the question you've linked here. My problem here is not the fact that I don't know how to put a collection view inside a TableView. If you see in the gif, this step is done, and it's fully working. I have many collection views in my TableView and that's not the problem here. Maybe I was not clear, but my concern here is that I don't know why on my third section, my collection viem seems to have a sort of reuse "bug" ? Because it's fully working, just not the first time I open the app, and this problem is not happenning when I have only 3 days for example – Omar Jbara Feb 18 '18 at 21:49
  • Thanks, but for the question you've linked here (https://stackoverflow.com/questions/35908574/reloading-collection-view-inside-a-table-view-cell-happens-after-all-cells-in-ta), it's not answering to my problem, because my way to deal with the probem he had in his question was to pass an `events` variable to my row, instead of setting the tag of the collection view. I really think here in my case it's related to a dequeueing issue, because it's working as expected, just not the first time, and only when my list of days exceed 3 in length, it's really weird I'll keep investigate – Omar Jbara Feb 18 '18 at 23:16
  • @matt ok I've solved my problem. Your article about separating concern was very helpful I think (http://matteomanferdini.com/the-correct-way-to-display-lists-in-ios-and-what-many-developers-do-wrong/#reuse). I'll edit my question, I think I've found why it was not working by doing some refactoring and by making my DataSource in it's own class – Omar Jbara Feb 19 '18 at 00:16

0 Answers0