David Seek David Seek - 2 months ago 8
Swift Question

Swift / TableView reusable Cell loads with different content every time it gets displayed

I'm having a

UITableViewController
. Inside section 1 there is a Cell displaying a JTAppleCalendar easily populated with:

if indexPath.section == 1 {

let cell = tableView.dequeueReusableCellWithIdentifier("calendarViewCell") as! CalendarViewCell

print(eventDatesAsNSDates)
cell.calendarView.selectDates(eventDatesAsNSDates)
return cell
}


eventDatesAsNSDates
gets populated within
viewDidAppear
.

In theory everything is working as I want it. But if I scroll the TableView down and up, following completely annoying behavior happens.

enter image description here

The print statement within cellForRowAtIndexPath
print(eventDatesAsNSDates)
proves that
eventDatesAsNSDates
does not change and yet the Calendar keeps being populated one time and not the other time and then being populated again...

Neither
cell.calendarView.selectDates
nor
eventDatesAsNSDates
gets set or called on another time in the App.

What am I missing? Help is very appreciated.

As requested, the selectDates function:

public func selectDates(dates: [NSDate], triggerSelectionDelegate: Bool = true, keepSelectionIfMultiSelectionAllowed: Bool = false) {
var allIndexPathsToReload: [NSIndexPath] = []
var validDatesToSelect = dates
// If user is trying to select multiple dates with multiselection disabled, then only select the last object
if !calendarView.allowsMultipleSelection && dates.count > 0 { validDatesToSelect = [dates.last!] }

let addToIndexSetToReload = {(indexPath: NSIndexPath)->Void in
if !allIndexPathsToReload.contains(indexPath) { allIndexPathsToReload.append(indexPath) } // To avoid adding the same indexPath twice.
}

let selectTheDate = {(indexPath: NSIndexPath, date: NSDate) -> Void in
self.calendarView.selectItemAtIndexPath(indexPath, animated: false, scrollPosition: .None)
addToIndexSetToReload(indexPath)
// If triggereing is enabled, then let their delegate handle the reloading of view, else we will reload the data
if triggerSelectionDelegate {
self.internalCollectionView(self.calendarView, didSelectItemAtIndexPath: indexPath)
} else { // Although we do not want the delegate triggered, we still want counterpart cells to be selected

// Because there is no triggering of the delegate, the cell will not be added to selection and it will not be reloaded. We need to do this here
self.addCellToSelectedSetIfUnselected(indexPath, date: date)
let cellState = self.cellStateFromIndexPath(indexPath, withDate: date)
if let aSelectedCounterPartIndexPath = self.selectCounterPartCellIndexPathIfExists(indexPath, date: date, dateOwner: cellState.dateBelongsTo) {
// If there was a counterpart cell then it will also need to be reloaded
addToIndexSetToReload(aSelectedCounterPartIndexPath)
}
}
}

let deSelectTheDate = { (oldIndexPath: NSIndexPath) -> Void in
addToIndexSetToReload(oldIndexPath)
if let index = self.theSelectedIndexPaths.indexOf(oldIndexPath) {
let oldDate = self.theSelectedDates[index]
self.calendarView.deselectItemAtIndexPath(oldIndexPath, animated: false)
self.theSelectedIndexPaths.removeAtIndex(index)
self.theSelectedDates.removeAtIndex(index)

// If delegate triggering is enabled, let the delegate function handle the cell
if triggerSelectionDelegate {
self.internalCollectionView(self.calendarView, didDeselectItemAtIndexPath: oldIndexPath)
} else { // Although we do not want the delegate triggered, we still want counterpart cells to be deselected
let cellState = self.cellStateFromIndexPath(oldIndexPath, withDate: oldDate)
if let anUnselectedCounterPartIndexPath = self.deselectCounterPartCellIndexPath(oldIndexPath, date: oldDate, dateOwner: cellState.dateBelongsTo) {
// If there was a counterpart cell then it will also need to be reloaded
addToIndexSetToReload(anUnselectedCounterPartIndexPath)
}
}
}
}

for date in validDatesToSelect {
let components = self.calendar.components([.Year, .Month, .Day], fromDate: date)
let firstDayOfDate = self.calendar.dateFromComponents(components)!

// If the date is not within valid boundaries, then exit
if !(firstDayOfDate >= self.startOfMonthCache && firstDayOfDate <= self.endOfMonthCache) { continue }
let pathFromDates = self.pathsFromDates([date])

// If the date path youre searching for, doesnt exist, then return
if pathFromDates.count < 0 { continue }
let sectionIndexPath = pathFromDates[0]

// Remove old selections
if self.calendarView.allowsMultipleSelection == false { // If single selection is ON
let selectedIndexPaths = self.theSelectedIndexPaths // made a copy because the array is about to be mutated
for indexPath in selectedIndexPaths {
if indexPath != sectionIndexPath { deSelectTheDate(indexPath) }
}

// Add new selections
// Must be added here. If added in delegate didSelectItemAtIndexPath
selectTheDate(sectionIndexPath, date)
} else { // If multiple selection is on. Multiple selection behaves differently to singleselection. It behaves like a toggle. unless keepSelectionIfMultiSelectionAllowed is true.
// If user wants to force selection if multiselection is enabled, then removed the selected dates from generated dates
if keepSelectionIfMultiSelectionAllowed {
if selectedDates.contains(calendar.startOfDayForDate(date)) {
addToIndexSetToReload(sectionIndexPath)
continue // Do not deselect or select the cell. Just add it to be reloaded
}
}
if self.theSelectedIndexPaths.contains(sectionIndexPath) { // If this cell is already selected, then deselect it
deSelectTheDate(sectionIndexPath)
} else {
// Add new selections
// Must be added here. If added in delegate didSelectItemAtIndexPath
selectTheDate(sectionIndexPath, date)
}
}
}


// If triggering was false, although the selectDelegates weren't called, we do want the cell refreshed. Reload to call itemAtIndexPath
if /*triggerSelectionDelegate == false &&*/ allIndexPathsToReload.count > 0 {
delayRunOnMainThread(0.0) {
self.batchReloadIndexPaths(allIndexPathsToReload)
}
}
}

Answer

Well the cellForRow delegate method is attempting to update tableView rows/section(s) each time your cell is visible again.

I would definitely prepare the data (for calendar) somewhere else and I'd prefer to render data only. Reusability of UITableViewCell will handle content properly.

Don't call that method in the cellForRow method. Try to place it e.g. into viewDidAppear() method. If selectDates() method does all the work of updating your tableView, it could still work.