UITableView dentro de UIScrollView usando el autolayout

Por el momento, estoy usando UITableView junto con otras vistas que están contenidas en UIScrollView . Quiero que UITableView tenga su altura para ser la misma que su altura de contenido.

Para complicar las cosas, también estoy insertando / eliminando filas para proporcionar un efecto de acordeón, de modo que cuando el usuario toque una fila, se muestren más detalles para esa fila.

He completado la inserción / eliminación, aunque en este momento no actualiza UIScrollView, que es su supervista, de modo que el tamaño del contenido de UIScrollView se vuelve a calcular y la UITableView junto con otras vistas en UIScrollView se muestran correctamente.

¿Cómo puedo implementar esto para que el tamaño de UIScrollView se ajuste y su contenido se distribuya correctamente cuando cambie el contenido de UITableView ? Actualmente estoy usando el diseño automático.

En primer lugar, ¿esas otras vistas (hermanos de la vista de tabla) están estrictamente por encima y debajo de la vista de tabla? De ser así, ¿ha considerado dejar que la vista de tabla se desplace normalmente y colocar esas vistas externas en las vistas de encabezado y pie de página de la vista de tabla? Entonces no necesita la vista de desplazamiento.

En segundo lugar, es posible que desee leer la Nota técnica TN2154: UIScrollView y Autolayout si aún no lo ha hecho.

En tercer lugar, dada la información en esa nota técnica, puedo pensar en algunas maneras de hacer lo que quiera. El más limpio es probablemente crear una subclase de UITableView que implemente el método intrinsicContentSize . La implementación es trivial:

 @implementation MyTableView - (CGSize)intrinsicContentSize { [self layoutIfNeeded]; // force my contentSize to be updated immediately return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height); } @end 

Luego solo permite que el diseño automático use el tamaño de contenido intrínseco de la vista de tabla. Cree las restricciones entre las subvistas de la vista de desplazamiento (incluida la vista de tabla) para disponerlas, y asegúrese de que haya restricciones para los cuatro bordes de la vista de desplazamiento.

Probablemente necesite enviar invalidateIntrinsicContentSize a la vista de tabla en los momentos apropiados (cuando agrega o quita filas o cambia las alturas de las filas). Probablemente pueda anular los métodos apropiados en MyTableView para hacer eso. Por ejemplo, do [self invalidateIntrinsicContentSize] en -endUpdates , -reloadData , - insertRowsAtIndexPaths:withRowAnimation: etc.

Este es el resultado de mi prueba:

vista de tabla con tamaño de contenido intrínseco en la vista de desplazamiento

La vista de desplazamiento tiene el fondo azul claro. La etiqueta roja superior y la etiqueta inferior azul son hermanos de la vista de tabla dentro de la vista de desplazamiento.

Aquí está el código fuente completo para el controlador de vista en mi prueba. No hay archivo xib.

 #import "ViewController.h" #import "MyTableView.h" @interface ViewController ()  @end @implementation ViewController - (void)loadView { UIView *view = [[UIView alloc] init]; self.view = view; UIScrollView *scrollView = [[UIScrollView alloc] init]; scrollView.translatesAutoresizingMaskIntoConstraints = NO; scrollView.backgroundColor = [UIColor cyanColor]; [view addSubview:scrollView]; UILabel *topLabel = [[UILabel alloc] init]; topLabel.translatesAutoresizingMaskIntoConstraints = NO; topLabel.text = @"Top Label"; topLabel.backgroundColor = [UIColor redColor]; [scrollView addSubview:topLabel]; UILabel *bottomLabel = [[UILabel alloc] init]; bottomLabel.translatesAutoresizingMaskIntoConstraints = NO; bottomLabel.text = @"Bottom Label"; bottomLabel.backgroundColor = [UIColor blueColor]; [scrollView addSubview:bottomLabel]; UITableView *tableView = [[MyTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; tableView.translatesAutoresizingMaskIntoConstraints = NO; tableView.dataSource = self; tableView.delegate = self; [scrollView addSubview:tableView]; UILabel *footer = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)]; footer.backgroundColor = [UIColor greenColor]; footer.text = @"Footer"; tableView.tableFooterView = footer; NSDictionary *views = NSDictionaryOfVariableBindings( scrollView, topLabel, bottomLabel, tableView); [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:nil views:views]]; [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:nil views:views]]; [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topLabel][tableView][bottomLabel]|" options:0 metrics:nil views:views]]; [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[topLabel]|" options:0 metrics:nil views:views]]; [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-8-[tableView]-8-|" options:0 metrics:nil views:views]]; [view addConstraint:[NSLayoutConstraint constraintWithItem:tableView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeWidth multiplier:1 constant:-16]]; [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[bottomLabel]|" options:0 metrics:nil views:views]]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 20; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"]; } cell.textLabel.text = [NSString stringWithFormat:@"Row %d", indexPath.row]; return cell; } @end 

Además de la respuesta de rob, hay un rápido ejemplo de la subclase de UITableView que se puede redimensionar:

Swift 2.x

 class IntrinsicTableView: UITableView { override var contentSize:CGSize { didSet { self.invalidateIntrinsicContentSize() } } override func intrinsicContentSize() -> CGSize { self.layoutIfNeeded() return CGSizeMake(UIViewNoIntrinsicMetric, contentSize.height) } } 

Swift 3.x o Swift 4.x

 class IntrinsicTableView: UITableView { override var contentSize:CGSize { didSet { self.invalidateIntrinsicContentSize() } } override var intrinsicContentSize: CGSize { self.layoutIfNeeded() return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height) } } 

Lo he usado para poner una vista de tabla en otra celda de vista de tabla de tamaño automático.

Aquí está la versión obj-C. Está basado en una solución del usuario @MuHAOS

 @implementation SizedTableView - (void)setContentSize:(CGSize)contentSize { [super setContentSize:contentSize]; [self invalidateIntrinsicContentSize]; } - (CGSize)intrinsicContentSize { [self layoutIfNeeded]; // force my contentSize to be updated immediately return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height); } @end 

El código de @ MuHAOS y @ klemen-zagar me ayudó mucho, pero en realidad causa un problema de rendimiento al activar un ciclo de diseño sin fin cuando la tabla está contenida dentro de una vista de stack que en sí misma está contenida en una vista de desplazamiento. Vea mi solución a continuación.

 @interface AutoSizingTableView () @property (nonatomic, assign) BOOL needsIntrinsicContentSizeUpdate; @end @implementation AutoSizingTableView - (void)setContentSize:(CGSize)contentSize { [super setContentSize:contentSize]; self.needsIntrinsicContentSizeUpdate = YES; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (!self.needsIntrinsicContentSizeUpdate) { return; } self.needsIntrinsicContentSizeUpdate = NO; [self layoutIfNeeded]; [self invalidateIntrinsicContentSize]; }); } - (CGSize)intrinsicContentSize { return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height); } @end 

puede agregar vista como vista de encabezado y pie de página de tabla vista. porque la vista de tabla es la subvista de scrollview. sigue el ejemplo a continuación.

 UILabel *topLabel = [[UILabel alloc] init]; topLabel.translatesAutoresizingMaskIntoConstraints = NO; topLabel.text = @"Top Label"; topLabel.backgroundColor = [UIColor redColor]; tableView.tableFooterView = topLabel; UILabel *footer = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)]; footer.backgroundColor = [UIColor greenColor]; footer.text = @"Footer"; tableView.tableFooterView = footer; 

y también puede agregar una vista de encabezado y pie de página de tabla vista usando la simple función de arrastrar y soltar a la vista de tabla en el guión gráfico y tomar IBOutlet de esas vistas.