I'm implementing collapsable section headers in a UITableViewController.
Here's how I determine how many rows to show per section:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.sections[section].isCollapsed ? 0 : self.sections[section].items.count
}
There is a struct that holds the section info with a bool for 'isCollapsed'.
Here's how I'm toggling their states:
private func getSectionsNeedReload(_ section: Int) -> [Int]
{
var sectionsToReload: [Int] = [section]
let toggleSelectedSection = !sections[section].isCollapsed
// Toggle collapse
self.sections[section].isCollapsed = toggleSelectedSection
if self.previouslyOpenSection != -1 && section != self.previouslyOpenSection
{
self.sections[self.previouslyOpenSection].isCollapsed = !self.sections[self.previouslyOpenSection].isCollapsed
sectionsToReload.append(self.previouslyOpenSection)
self.previouslyOpenSection = section
}
else if section == self.previouslyOpenSection
{
self.previouslyOpenSection = -1
}
else
{
self.previouslyOpenSection = section
}
return sectionsToReload
}
internal func toggleSection(_ header: CollapsibleTableViewHeader, section: Int)
{
let sectionsNeedReload = getSectionsNeedReload(section)
self.tableView.beginUpdates()
self.tableView.reloadSections(IndexSet(sectionsNeedReload), with: .automatic)
self.tableView.endUpdates()
}
Everything is working and animating nicely, however in the console when collapsing an expanded section, I get this [Assert]:
[Assert] Unable to determine new global row index for preReloadFirstVisibleRow (0)
This happens, regardless of whether it's the same opened Section, closing (collapsing), or if I'm opening another section and 'auto-closing' the previously open section.
I'm not doing anything with the data; that's persistent.
Could anyone help explain what's missing? Thanks
On the Home tab, click the arrow in the Paragraph group. In the Paragraph dialog box, click the checkbox next to Collapsed by default. Click OK.
Place your cursor in the heading. On the HOME tab, in the Paragraph group, click the dialog box launcher. In the Paragraph dialog box, click the checkbox next to Collapsed by default. Click OK.
In order for a tableView to know where it is while it's reloading rows etc, it tries to find an "anchor row" which it uses as a reference. This is called a preReloadFirstVisibleRow
. Since this tableView might not have any visible rows at some point because of all the sections being collapsed, the tableView will get confused as it can't find an anchor. It will then reset to the top.
Solution:
Add a 0 height row to every group which is collapsed. That way, even if a section is collapsed, there's a still a row present (albeit of 0px height). The tableView then always has something to hook onto as a reference. You will see this in effect by the addition of a row in numberOfRowsInSection
if the rowcount is 0 and handling any further indexPath.row
calls by making sure to return the phatom cell value before indexPath.row
is needed if the datasource.visibleRows
is 0.
It's easier to demo in code:
func numberOfSections(in tableView: UITableView) -> Int {
return datasource.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasource[section].visibleRows.count == 0 ? 1 : datasource[section].visibleRows.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
datasource[section].section = section
return datasource[section]
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if datasource[indexPath.section].visibleRows.count == 0 { return 0 }
return datasource[indexPath.section].visibleRows[indexPath.row].bounds.height
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if datasource[indexPath.section].visibleRows.count == 0 { return UITableViewCell() }
// I've left this stuff here to show the real contents of a cell - note how
// the phantom cell was returned before this point.
let section = datasource[indexPath.section]
let cell = TTSContentCell(withView: section.visibleRows[indexPath.row])
cell.accessibilityLabel = "cell_\(indexPath.section)_\(indexPath.row)"
cell.accessibilityIdentifier = "cell_\(indexPath.section)_\(indexPath.row)"
cell.showsReorderControl = true
return cell
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With