===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).
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!