Secciones plegables: [Afirmar] No se puede determinar el nuevo índice de fila global para preReloadFirstVisibleRow (0)

9

Estoy implementando encabezados de sección plegables en un UITableViewController.

Así es como determino cuántas filas mostrar por sección:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return self.sections[section].isCollapsed ? 0 : self.sections[section].items.count
}

Hay una estructura que contiene la información de la sección con un bool para 'isCollapsed'.

Así es como estoy alternando sus estados:

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()
}

Todo funciona y se anima muy bien, sin embargo, en la consola al colapsar una sección expandida, obtengo esto [Afirmar]:

[Afirmar] No se puede determinar el nuevo índice de fila global para preReloadFirstVisibleRow (0)

Esto sucede, independientemente de si es la misma Sección abierta, cerrando (colapsando), o si estoy abriendo otra sección y 'cerrando automáticamente' la sección abierta anteriormente.

No estoy haciendo nada con los datos; Eso es persistente.

¿Alguien podría ayudar a explicar lo que falta? Gracias

iOSProgrammingIsFun
fuente
¿Su vista de tabla está compuesta por un montón de secciones y no muchas filas reales?
Byron Coetsee
¿Alguna vez lograste arreglar esto?
PaulDoesDev
@ByronCoetsee Sí, hasta que se expanda una sección. Entonces, cuando todo colapsó, solo son encabezados de sección. Cuando se expande, todos son encabezados de sección para las secciones no expandidas y un encabezado de sección y luego celdas para datos.
iOSProgrammingIsFun
@PaulDoesDev Lo hice, pero no utilizando este mecanismo. Lo reescribí por completo para que, aunque parezca lo mismo, funcione de manera completamente diferente. Sin embargo, voy a dejar esto aquí en caso de que alguien pueda arreglarlo con elegancia, o que ayude a otros de alguna manera.
iOSProgrammingIsFun
1
@iOSProgrammingIsFun jaja sí, pensé que podría parecer un truco y técnicamente lo es, pero la cantidad de código y el hecho de que en realidad está bastante limpio significa que puedo dejarme dormir por la noche: código P publicado a continuación
Byron Coetsee

Respuestas:

8

Para que un TableView sepa dónde está mientras está recargando filas, etc., intenta encontrar una "fila de anclaje" que utiliza como referencia. Esto se llama a preReloadFirstVisibleRow. Dado que esta tabla de vista puede no tener filas visibles en algún momento debido a que todas las secciones están colapsadas, la tabla de vista se confundirá ya que no puede encontrar un ancla. Luego se restablecerá a la parte superior.

Solución: agregue una fila de altura 0 a cada grupo que esté contraído. De esa manera, incluso si una sección se contrae, todavía hay una fila presente (aunque de 0px de altura). El tableView siempre tiene algo para enganchar como referencia. Verá esto en efecto agregando una fila numberOfRowsInSectionsi el número de filas es 0 y manejando cualquier indexPath.rowllamada adicional asegurándose de devolver el valor de celda falso antes de que indexPath.rowsea ​​necesario si eldatasource.visibleRows es 0.

Es más fácil hacer una demostración en código:

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
}
Byron Coetsee
fuente