EXC_BAD_ACCESS en heightForRowAtIndexPath iOS

Estoy trabajando en una aplicación donde tengo una subclase personalizada de UITableViewCell. Quiero hacer que la altura de la celda sea dinámica en función del texto que contiene. Intento hacer eso en mi método heightForRowAtIndexPath. Pero tengo algunos problemas, el siguiente código provoca un error EXC_BAD_ACCESS (code = 2 address = 0xb7ffffcc).

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { PostCell *cell = (PostCell *)[tableView cellForRowAtIndexPath:indexPath]; CGSize postTextSize; if (![cell.postText.text isEqualToString:@""]) { postTextSize = [cell.postText.text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(cell.postText.frame.size.width, 200)]; } else { postTextSize = CGSizeMake(cell.postText.frame.size.width, 40); } return postTextSize.height + cell.userName.frame.size.height + 10; } 

Si en cambio tengo:

 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 100; } 

Funciona.

Parece estar fallando en esta línea: PostCell *cell = (PostCell *)[tableView cellForRowAtIndexPath:indexPath]; y no sé por qué Intenté construir limpio y reiniciar el simulador, pero ninguno funcionó. ¿Alguna idea de por qué está pasando esto?

He intentado reproducir el problema. Resulta que llamar a cellForRowAtIndexPath: dentro de heightForRowAtIndexPath hace que heightForRowAtIndexPath se llame de forma recursiva. Aquí hay un extracto de la traza inversa detrás de los 3 pasos de recursión:

 frame #0: 0x000042d0 DocInteraction`-[DITableViewController tableView:heightForRowAtIndexPath:] + 48 at DITableViewController.m:262 frame #1: 0x0054f688 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 3437 frame #2: 0x0055040f UIKit`-[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 144 frame #3: 0x00551889 UIKit`-[UITableViewRowData numberOfRows] + 137 frame #4: 0x00553dac UIKit`-[UITableViewRowData globalRowsInRect:] + 42 frame #5: 0x003f82eb UIKit`-[UITableView(_UITableViewPrivate) _visibleGlobalRowsInRect:] + 177 frame #6: 0x004001e6 UIKit`-[UITableView cellForRowAtIndexPath:] + 113 frame #7: 0x000042f2 DocInteraction`-[DITableViewController tableView:heightForRowAtIndexPath:] + 82 at DITableViewController.m:262 frame #8: 0x0054f688 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 3437 frame #9: 0x0055040f UIKit`-[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 144 frame #10: 0x00551889 UIKit`-[UITableViewRowData numberOfRows] + 137 frame #11: 0x00553dac UIKit`-[UITableViewRowData globalRowsInRect:] + 42 frame #12: 0x003f82eb UIKit`-[UITableView(_UITableViewPrivate) _visibleGlobalRowsInRect:] + 177 frame #13: 0x004001e6 UIKit`-[UITableView cellForRowAtIndexPath:] + 113 frame #14: 0x000042f2 DocInteraction`-[DITableViewController tableView:heightForRowAtIndexPath:] + 82 at DITableViewController.m:262 frame #15: 0x0054f688 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 3437 frame #16: 0x0055040f UIKit`-[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 144 frame #17: 0x00551889 UIKit`-[UITableViewRowData numberOfRows] + 137 frame #18: 0x003ff66d UIKit`-[UITableView noteNumberOfRowsChanged] + 119 frame #19: 0x003ff167 UIKit`-[UITableView reloadData] + 764 

Finalmente el progtwig se cuelga. En mi simulador, esto sucede cuando la traza de atrás tiene unos 57000 niveles de profundidad.


Respuesta anterior (no está mal, pero no explica el EXC_BAD_ACCESS):

El problema es ese

 [tableView cellForRowAtIndexPath:indexPath] 

devuelve nil para las filas que actualmente no están visibles. Una vista de tabla asigna solo tantas celdas que se requieren para mostrar las filas visibles actualmente. Las celdas se reutilizan cuando se desplaza por la vista de tabla.

Pero se llama a heightForRowAtIndexPath para todas las celdas de la vista de tabla antes de que se muestre cualquier fila.

Como consecuencia, no debe obtener el texto de las celdas de la vista de tabla para calcular la altura en heightForRowAtIndexPath . Debería obtener el texto de su fuente de datos en su lugar.

tableView heightForRowAtIndexPath se llama antes de cellForRowAtIndexPath , antes de que se muestre una celda, primero se debe calcular la altura.

debería obtener text de su fuente de datos, no de la cell

Tuve un problema similar y me crucé, solo para compartir lo que hago ahora que me funciona bien

 CGFloat mycell_height; - (void)viewDidLoad { [super viewDidLoad]; UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCell"]; mycell_height = cell.frame.size.height; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return mycell_height; //assuming all cells are the same } 

Espero que esto ayude a cualquiera que busque esto y lo nuevo en la progtwigción de iOS como yo 🙂

En este caso, debe llamar al método UITableViewDataSource para obtener la celda para indexPath en lugar de cellForRowAtIndexPath: método de UITableView porque en el momento cuando tableView: heightForRowAtIndexPath: primera vez que se llama no hay celdas en tableView.

 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath *)indexPath { PostCell *cell = (PostCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath]; CGSize postTextSize; if (![cell.postText.text isEqualToString:@""]) { postTextSize = [cell.postText.text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(cell.postText.frame.size.width, 200)]; } else { postTextSize = CGSizeMake(cell.postText.frame.size.width, 40); } return postTextSize.height + cell.userName.frame.size.height + 10; } 

Funciona y no causa la excepción EXC_BAD_ACCESS.

RÁPIDO:

Use esto para saber cuándo se cargaron las tablas:

 extension UITableView { func reloadData(completion: ()->()) { UIView.animateWithDuration(0, animations: { self.reloadData() }) { _ in completion() } } } 

Crear un valor booleano

 var tables_loaded = Bool(false) 

Luego en viewDidLoad:

 tableView.reloadData { tables_loaded = true // call functions or other stuff regarding table manipulation. // tableView.beginUpdates() // tableView.endUpdates() } 

luego en

 override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { if tables_loaded { let cell = tableView.cellForRowAtIndexPath(indexPath) // Do your magic here! } }