Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collapsable Sections: [Assert] Unable to determine new global row index for preReloadFirstVisibleRow (0)

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

like image 806
iOSProgrammingIsFun Avatar asked Oct 03 '19 19:10

iOSProgrammingIsFun


People also ask

How do I get rid of expand collapse in Word?

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.

How do I make collapsible sections in Word?

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.


1 Answers

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
}
like image 107
Byron Coetsee Avatar answered Oct 18 '22 06:10

Byron Coetsee