Swift – tableView Actualizaciones de altura de fila solo después de desplazarse o alternar expandir / contraer

Estoy usando CollapsibleTableView desde aquí y lo modifiqué según mi requerimiento para lograr secciones plegables. Así es como se ve ahora .

Dado que hay un borde para mi sección según el layout de la interfaz de usuario, elegí el encabezado de la sección como mi elemento de la interfaz de usuario que contiene datos tanto en modos queuepsados ​​como expandidos.

Motivo: intenté pero no pude conseguir que funcionara en este model que se explica a continuación:

** Tener los elementos de encabezado en el encabezado de la sección y los detalles de cada elemento en su celda. Por defecto, la sección está en estado queuepsado. Cuando el usuario toca el encabezado, la celda se muestra para mostrar. Como dije, dado que hay un borde que se debe mostrar a toda la sección (encabezado con el dedo y su celda), elegí el encabezado de la sección como mi elemento de operación de interfaz de usuario. Aquí está mi código para tableView:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sections.count } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { switch indexPath.row { case 0: return sections[indexPath.section].collapsed! ? 0 : (100.0 + heightOfLabel2!) case 1: return 0 case 2: return 0 default: return 0 } } func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let header = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("header") as! CollapsibleTableViewHeader if sections.count == 0 { self.tableView.userInteractionEnabled = false header.cornerRadiusView.layer.borderWidth = 0.0 header.benefitAlertImage.hidden = true header.benefitAlertText.hidden = true header.amountLabel.hidden = true header.titleLabel.text = "No_Vouchers".localized() } else { header.amountLabel.hidden = false header.cornerRadiusView.layer.borderWidth = 1.0 self.tableView.userInteractionEnabled = true header.titleLabel.text = sections[section].name header.arrowImage.image = UIImage(named: "voucherDownArrow") header.setCollapsed(sections[section].collapsed) let stringRepresentation = sections[section].items.joinWithSeparator(", ") header.benefitDetailText1.text = stringRepresentation header.benefitDetailText2.text = sections[section].shortDesc header.benefitDetailText3.text = sections[section].untilDate header.section = section header.delegate = self if sections[section].collapsed == true { header.benefitAlertImage.hidden = true header.benefitAlertText.hidden = true } else { if sections[section].isNearExpiration == true { header.benefitAlertImage.hidden = false header.benefitAlertText.hidden = false } else { header.benefitAlertImage.hidden = true header.benefitAlertText.hidden = true } } if appLanguageDefault == "nl" { self.totalAmountLabel.text = "€ \(sections[section].totalAvailableBudget)" } else { self.totalAmountLabel.text = "\(sections[section].totalAvailableBudget) €" } } return header } 

Función para alternar queuepso / expansión : estoy usando valores de altura de los UILabels "dinámicamente cambiantes" dentro de la sección y luego usando esos valores para extender el borde (usando su layout de layout).

 func toggleSection(header: CollapsibleTableViewHeader, section: Int) { let collapsed = !sections[section].collapsed header.benefitAlertImage.hidden = true header.benefitAlertText.hidden = true // Toggle collapse sections[section].collapsed = collapsed header.setCollapsed(collapsed) // Toggle Alert Labels show and hide if sections[section].collapsed == true { header.cornerRadiusViewBtmConstraint.constant = 0.0 header.cornerRadiusViewTopConstraint.constant = 20.0 header.benefitAlertImage.hidden = true header.benefitAlertText.hidden = true } else { heightOfLabel2 = header.benefitDetailText2.bounds.size.height if sections[section].isNearExpiration == true { header.benefitAlertImage.hidden = false header.benefitAlertText.hidden = false header.cornerRadiusViewBtmConstraint.constant = -100.0 - heightOfLabel2! header.cornerRadiusViewTopConstraint.constant = 10.0 if let noOfDays = sections[section].daysUntilExpiration { if appLanguageDefault == "nl" { header.benefitAlertText.text = "(nog \(noOfDays) dagen geldig)" } else { header.benefitAlertText.text = "(valable encore \(noOfDays) jour(s))" } } } else { header.cornerRadiusViewBtmConstraint.constant = -80.0 - heightOfLabel2! header.cornerRadiusViewTopConstraint.constant = 20.0 header.benefitAlertImage.hidden = true header.benefitAlertText.hidden = true } } // Adjust the height of the rows inside the section tableView.beginUpdates() for i in 0 ..< sections.count { tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: i, inSection: section)], withRowAnimation: .Automatic) } tableView.endUpdates() } 

El problema: Necesito tener algunos encabezados de sección en esta vista de tabla para expandirse por defecto en el primer lanzamiento de la vista, en function de algunas condiciones. Como estoy calculando la altura de las tags y utilizando las alturas para establecer la restricción superior e inferior del borde, se ha vuelto difícil mostrar el encabezado de la sección expandida según el layout.

El contenido sale del límite ya que la altura de mi UILabel se toma como 21 por defecto.

ACTUALIZACIÓN : la altura de la fila cambia solo después de desplazarme por la vista o cuando alterno entre queuepsar / expandir

La pregunta: ¿Cómo calculo las alturas de UILabels presentes en el encabezado de mi Sección por primera vez en el lanzamiento de la vista? (Esto significa que, una vez que termine mi llamada REST, se recuperarán los datos y tendré que get la altura de UIlabel).

Actualmente, estoy usando heightOfLabel2 = header.benefitDetailText2.bounds.size.height

(O)

¿Hay una mejor manera de lograr esto?

¡Gracias por adelantado!

Puede probar esto para la extensión String para calcular el rectángulo delimitador.

 extension String { func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat { let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil) return boundingBox.height } } 

Fuente: Calcule el tamaño de UILabel basado en String en Swift

Esto es lo que obtuve trabajando en base a mi comprensión de los objectives generales de OP. Si no entiendo, lo siguiente sigue siendo un ejemplo de trabajo. El proyecto de trabajo completo también está vinculado a continuación.

Metas:

  • TablaViewCells de tamaño dynamic que también son
  • Colapsable para mostrar u ocultar detalles adicionales.

Probé varias maneras diferentes, esta es la única que pude conseguir.

Visión de set

El layout hace uso de lo siguiente:

  • tablaViewCells personalizada
  • Autolayout
  • Dimensión automática de TableView

Por lo tanto, si no está familiarizado con esos (especialmente Autolayout, quizás desee revisarlos primero).

TableViewCells dynamic

Generador de interfaces

Diseña tu celda prototipo. Es más fácil boost el tamaño de la altura de la fila. Comience simplemente con solo algunos elementos para asegurarse de que pueda hacerlo funcionar. (aunque agregar en Autolayout puede ser un dolor). Por ejemplo, simplemente apile dos tags verticalmente, ancho completo del layout. Haga la label superior 1 línea para el "título" y la segunda 0 líneas para los "detalles"

Importante: para configurar tags y áreas de text para que crezcan al tamaño de su contenido, debe establecer que las tags tengan 0 líneas y áreas de text para que no se puedan desplazar. Esos son los factores desencadenantes para el ajuste a los contenidos.

Lo más importante es asegurarse de que haya una restricción para los cuatro lados de cada elemento. Esto es esencial para que el dimensionamiento automático funcione.

Colapsable celda

Luego hacemos una class personalizada muy básica para ese prototipo de celda de tabla. Conecte las tags a las salidas en la class de celda personalizada. Ex:

 class CollapsableCell: UITableViewCell { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var detailLabel: UILabel! } 

Comenzar simplemente con dos tags es más fácil.

Asegúrese también de que en Interface Builder configure la class de celda de prototipo en CollapsableCell y le proporcione una ID de reutilización.

CollapsableCellViewController

En el ViewController. Primero, las cosas estándar para TableViewCells personalizados:

  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return data.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "collapsableCell", for: indexPath) as! CollapsableCell let item = data[indexPath.row] cell.titleLabel?.text = item.title cell.detailLabel?.text = item.detail return cell } 

Hemos agregado funciones para devolver el número de filas y devolver una celda para una Fila determinada utilizando nuestra Celda personalizada. Espero que todo sea sencillo.

Ahora normalmente habría una function más, TableView (heightForRowAt :), que sería necesaria, pero no agregue eso (o sáquelo si lo tiene). Aquí es donde entra Auto Dimension. Agregue lo siguiente para verDidLoad:

  override func viewDidLoad() { ... // settings for dynamic resizing table cells tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = 50 ... } 

En este punto, si configura la label de detalle para que sea 0 líneas como se describe arriba y ejecuta el proyecto, debe get celdas de diferentes tamaños en function de la cantidad de text que está colocando en esa label. Ese Dynamic TableViewCells hecho.

Celdas plegables

Para agregar la funcionalidad de queuepso / expansión, podemos build el dimensionamiento dynamic que tenemos en este momento. Agregue la siguiente function al ViewController:

  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let cell = tableView.cellForRow(at: indexPath) as? CollapsableCell else { return } let item = data[indexPath.row] // update fields cell.detailLabel.text = self.isExpanded[indexPath.row] ? item.detail1 : "" // update table tableView.beginUpdates() tableView.endUpdates() // toggle hidden status isExpanded[indexPath.row] = !isExpanded[indexPath.row] } 

Agregue también 'var isExpanded = Bool' a su ViewController para almacenar el estado expandido actual para sus filas (Esto también podría ser una variable de class en su TableViewCell personalizado).

Genere y click una de las filas, debe networkingucirse para mostrar solo la label del título. Y eso es lo básico.

Proyecto de muestra: un proyecto de muestra de trabajo con algunos campos más y una image de galón de revelación está disponible en github . Esto también incluye una vista separada con una demostración de un cambio de tamaño dynamic de Stackview basado en contenido.

Algunas notas:

  • Todo esto se hace en TableViewCells normal. Sé que el OP estaba utilizando las celdas de encabezado, y aunque no puedo pensar en una razón por la que eso no funcione de la misma manera, no hay necesidad de hacerlo de esa manera.
  • Agregar y quitar una subView es el método que originalmente pensé que funcionaría mejor y sería más eficiente ya que una vista podría cargarse desde una punta, e incluso almacenarse listo para volverse a agregar. Por alguna razón, no pude lograr que esto se networkingimensionara después de agregar los subViews. No puedo pensar en una razón por la que no funcionaría, pero aquí hay una solución que sí funciona.

Si entendí tu pregunta correctamente, lo que quieres hacer es cambiar el tamaño de tu tableHeaderView cuando llames a toggleSection .

Por lo tanto, lo que debe hacer para que tableHeaderView tamaño es este

 // get the headerView let headerView = self.tableView(self.tableView, viewForHeaderInSection: someSection) // tell the view that it needs to refresh its layout headerView?.setNeedsDisplay() // reload the tableView tableView.reloadData() /* or */ // beginUpdates, endUpdates 

Básicamente, lo que haría sería colocar el fragment de código anterior dentro de su function toggleSection(header: CollapsibleTableViewHeader, section: Int)

 func toggleSection(header: CollapsibleTableViewHeader, section: Int) { ... // I'm not sure if this `header` variable is the headerView so I'll just add my code snippet at the bottom header.setNeedsDisplay() /* code snippet start */ // get the headerView let headerView = self.tableView(self.tableView, viewForHeaderInSection: someSection) // tell the view that it needs to refresh its layout headerView?.setNeedsDisplay() /* code snippet end */ // reload the tableView // Adjust the height of the rows inside the section tableView.beginUpdates() // You do not need this for i in 0 ..< sections.count { tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: i, inSection: section)], withRowAnimation: .Automatic) } // You do not need this tableView.endUpdates() } 

Explicación: Un headerView / footerView de tableView no actualiza su layout incluso si llama a reloadData() y beginUpdates,endUpdates . tableView decirle a la vista que primero debe actualizar y luego actualizar la tableView

Finalmente , también debes aplicar estos dos códigos

 func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { return estimatedHeight } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return UITableViewAutomaticDimension } 

En este metodo,

 func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { switch indexPath.row { case 0: return sections[indexPath.section].collapsed! ? 0 : (100.0 + heightOfLabel2!) case 1: return 0 case 2: return 0 default: return 0 } } 

en lugar de usar heightOfLabel2 , intente implementar el siguiente método para calcular alturas específicas de cada celda (ya que sabemos que el text se debe rellenar, su fuente y el ancho de la label, podemos calcular el alto de la label)

 func getHeightForBenefitDetailText2ForIndexPath(indexPath: NSIndexPath)->CGFloat 

Entonces su método debería verse así,

 func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { switch indexPath.row { case 0: return sections[indexPath.section].collapsed! ? 0 : (100.0 + getHeightForBenefitDetailText2ForIndexPath(indexPath)) case 1: return 0 case 2: return 0 default: return 0 } } 

Y respecto de su problema para expandir algunas celdas por primera vez, asegúrese de establecer la propiedad collapsed en true para esas celdas antes de volver a cargar la tabla.

Como una mejora del performance, puede almacenar el valor de altura calculado para cada celda expandida en un dictionary y devolver el valor del dictionary, para evitar el mismo cálculo una y otra vez.

Espero que esto te ayude. Si no es así, comparta un proyecto de ejemplo para get más información acerca de su problema.