2012-09-29 32 views
25

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).EXC_BAD_ACCESS en heightForRowAtIndexPath iOS

- (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 que se está cayendo 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?

Respuesta

52

He intentado reproducir el problema. Resulta que llamar al cellForRowAtIndexPath: dentro de heightForRowAtIndexPath hace que se llame heightForRowAtIndexPath recursivamente. Aquí es un extracto de la traza de pila después de las etapas 3 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 programa se bloquea. En mi simulador, esto sucede cuando la traza de atrás tiene unos 57000 niveles de profundidad.


respuesta Antiguo (no está mal, pero no explica el EXC_BAD_ACCESS):

El problema es que

[tableView cellForRowAtIndexPath:indexPath] 

vuelve nil para las filas que en la actualidad no son 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 heightForRowAtIndexPath se llama para todos celdas de la vista de tabla antes de que se muestre ninguna 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.

+0

¿Por qué mecanismo el método devuelve nil causa un EXC_BAD_ACCESS? – grahamparks

+1

@grahamparks: ¡Buena pregunta! Intenté reproducir el problema y agregué la información a mi respuesta. –

+0

Gracias, utilizando el origen de datos en su lugar trabajado. – Anders

7

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

usted debe conseguir text del origen de datos, no desde cell

2

tuve un problema similar y se encontró, sólo para compartir lo que hago ahora, que funciona bien para mí

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 
} 

Esperanza esta ayuda a cualquiera que busque esto y lo nuevo en iOS como yo :-)

0

En este caso, debe llamar al método UITableViewDataSource para obtener la celda para indexPath en lugar de cellForRowAtIndexPath: método de UITableView bec ause en el momento cuando tableView: heightForRowAtIndexPath: llamada por primera vez, 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 provoca EXC_BAD_ACCESS excepción.

+0

Funciona para mí, pero significa que el método UITableViewDataSource funciona antes de que las celdas se creen en la tabla vista, ¿verdad? @ninjaproger –

-1

SWIFT:

Uso esto para saber cuando las tablas se han cargado:

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

entonces en

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

} 
+0

No relacionado, pero 'Bool (falso)' parece inútil: 'falso' es correcto y Swift puede inferir su tipo. Me interesa saber por qué configuraría una 'var' con el inicializador' Bool'. – vrwim

+1

@vrwim Porque soy un novato en Swift. =) – Philip

Cuestiones relacionadas