Vuelva a clasificar según el tamaño UITableViewCell asíncronamente contra UIView-Encapsulated-Layout-Height generado

Estoy intentando cambiar el tamaño de una celda de vista de tabla "desde adentro", usando iOS 8, por lo tanto, no implemento

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; 

pero usando:

 self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.estimatedRowHeight = 200; 

La celda tiene restricciones de arriba a abajo.

La celda contiene (entre otras cosas) una UIWebview que conoce su tamaño de forma asíncrona después de la carga preguntando a scrollview su altura:

 CGFloat fittingHeight = self.messageTextWebView.scrollView.contentSize.height; 

(esta es una variante donde la vista web está dentro de otra vista y esa vista cambia de tamaño en consecuencia).

Como todo el process de layout automático finaliza cuando sucede esto, establezco

 [self setNeedsUpdateConstraints]; 

en la vista en la celda que burbujea hasta la celda y hacia abajo nuevamente con layoutSubviews .

Al final, obtengo el temible Unable to simultaneously satisfy constraints. . La restricción que rompe todo es

 "<NSLayoutConstraint:0x7f8c29d157f0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f8c2b063eb0(270)]>" 

Esta restricción se crea mediante la vista de tabla cuando la célula se calcula por primera vez.

Mata a la mía:

 Will attempt to recover by breaking constraint <NSLayoutConstraint:0x7fa8a8515b70 V:[UIView:0x7fa8aa0263e0(263)]> 

Aunque establecí las prioridades de mis restricciones en 1000, así como la resistencia de compression a 1000, no puedo anular la restricción creada por la vista de tabla.

Lo que es frustrante: si reemploop la vista web con una label multilínea simple y conozco todos los tamaños durante el layout de la primera celda, todo funciona perfectamente. Es la naturaleza asíncrona del sistema de carga de la vista web que me obliga a cambiar el layout después de que se renderiza la celda por primera vez.

Ahora mi pregunta:

¿Hay alguna forma de evitar esto? Realmente no quiero volver a cargar la celda. Si la celda es la única en la pantalla, no se está reutilizando y todo vuelve a empezar. ¿Es posible esto o la architecture de UITableView depende de esa restricción externa para renderizarse correctamente?

Solo por diversión, pongo

 self.translatesAutoresizingMaskIntoConstraints = NO; 

en la propia celda, dando como resultado una celda que tiene un tamaño de 0,0 y la restricción:

 "<NSLayoutConstraint:0x7f88506663a0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f8850648400(0)]>" 

pero aún está allí …

gracias por tu ayuda.

BTW: Ya leí todo lo que hay sobre el asunto, que incluye: Uso del layout automático en UITableView para layouts de celdas dinámicas y alturas de filas variables

Aquí hay una subclass UITableViewCell que administra una UIWebView y ajusta su altura correctamente para que un set UITableViewAutomaticDimension use UITableViewAutomaticDimension .

Principalmente, mantenga una NSLayoutConstraint para la altura UIWebView . Actualiza su constante en updateConstraints. Dígale a la table de padres que recalcite las alturas de las celdas cuando haya cambios.

 @implementation WebTableViewCell { IBOutlet UIWebView* _webview; // glued to cell content view using edge constraints IBOutlet NSLayoutConstraint* _heightConstraint; // height constraint for the webview itself } - (void) awakeFromNib { _webview.scrollView.scrollEnabled = NO; // use this to test various document heights // [_webview loadHTMLString: @"<html><body style='height:100px;'>Hello, World</body></html>" baseURL: nil]; // or, a real web page [_webview loadRequest: [NSURLRequest requestWithURL: [NSURL URLWithString: @"http://stackoverflow.com/users/291788/tomswift"]]]; } - (void) updateConstraints { [super updateConstraints]; // get the document height. CGFloat height = [[_webview stringByEvaluatingJavaScriptFromString: @"document.height"] floatValue]; if ( _heightConstraint.constant != height ) { // update _heightConstraint.constant = height; // ask the tableview to recalc heights dispatch_async(dispatch_get_main_queue(), ^{ UITableView* tableView = (UITableView*)self.superview; while ( tableView != nil && ![tableView isKindOfClass: [UITableView class]] ) tableView = (UITableView*)tableView.superview; [tableView beginUpdates]; [tableView endUpdates]; }); } } - (void) webViewDidFinishLoad:(UIWebView *)webView { [self setNeedsUpdateConstraints]; } @end 

Después de un poco más de investigación, mi conclusión es:

Utiliza cadenas atribuidas que ahora permiten contenido HTML (desde iOS7):

 [[NSAttributedString alloc] initWithData:[message dataUsingEncoding:NSUTF8StringEncoding] options: @{ NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding] } documentAttributes:nil error:&err]; 

El analizador HTML está bien, las limitaciones son

  • nunca debe usar tags src que hagan reference al contenido en la networking. El contenido se carga en el hilo principal. Me imagino que tuvieron que hacer eso para forzar el renderizado síncrono.
  • no hay interacción (que en realidad es una buena cosa).

O bien: una vista de colección podría funcionar, pero aún no he convertido la vista de tabla, según mis resultados de testing con NSAttributedString, podría omitir eso …

Actualizar

Intenté usar la fantástica https://github.com/TTTAttributedLabel/TTTAttributedLabel Hace que las cadenas atribuidas tengan la ventaja adicional de detectar enlaces. Lo que era todo lo que necesitaba, casi. No hace tags …

Volví a UITextView, que sorprendentemente funciona bien.