¿Cómo configuro la altura de tableHeaderView (UITableView) con autolayout?

He estado golpeando mi cabeza contra la pared con esto durante las últimas 3 o 4 horas y parece que no puedo entenderlo. Tengo un UIViewController con una pantalla completa UITableView dentro (hay algunas otras cosas en la pantalla, por lo que no puedo usar un UITableViewController) y quiero que mi tableHeaderView cambie el tamaño con el autolayout. Huelga decir que no está cooperando.

Vea la captura de pantalla a continuación.

enter image description here

Debido a que la descripción general (por ejemplo, el texto “Información general de la lista aquí.”) Tiene contenido dynamic, estoy usando la función de autodiseño para cambiar su tamaño y su super visión. Tengo todo el cambio de tamaño muy bien, a excepción de la tabla HeaderView, que está justo debajo de Paralax Table View en la jerarquía.

La única forma que he encontrado para cambiar el tamaño de esa vista de encabezado es programáticamente, con el siguiente código:

CGRect headerFrame = self.headerView.frame; headerFrame.size.height = headerFrameHeight; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView:self.headerView]; 

En este caso, headerFrameHeight es un cálculo manual de la altura tableViewHeader de la siguiente manera (innerHeaderView es el área blanca, o la segunda “vista”, headerView es tableHeaderView) :

 CGFloat startingY = self.innerHeaderView.frame.origin.y + self.overviewLabel.frame.origin.y; CGRect overviewSize = [self.overviewLabel.text boundingRectWithSize:CGSizeMake(290.f, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: self.overviewLabel.font} context:nil]; CGFloat overviewHeight = overviewSize.size.height; CGFloat overviewPadding = ([self.overviewLabel.text length] > 0) ? 10 : 0; // If there's no overviewText, eliminate the padding in the overall height. CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f); 

El cálculo manual funciona, pero es torpe y propenso a errores si las cosas cambian en el futuro. Lo que quiero poder hacer es tener el tamaño automático de tableHeaderView basado en las restricciones proporcionadas, como puede hacerlo en cualquier otro lugar. Pero por mi vida, no puedo resolverlo.

Hay varias publicaciones sobre SO sobre esto, pero ninguna es clara y terminó confundiéndome más. Aquí hay algunos:

  • Diseño automático en UITableViewHeader

  • Diseño automático para tableHeaderView

  • ¿Es posible utilizar AutoLayout con tableHeaderView de UITableView?

  • La altura de la vista de encabezado de la tabla es incorrecta cuando se usa el diseño automático, IB y tamaños de fuente

Realmente no tiene sentido cambiar la propiedad translateAttrasizingMaskIntoConstraints a NO, ya que eso solo causa errores para mí y no tiene sentido conceptualmente de todos modos.

¡Cualquier ayuda realmente sería apreciada!

EDITOR 1: Gracias a la sugerencia de TomSwift, pude descifrarlo. En lugar de calcular manualmente la altura de la vista general, puedo hacer que se calcule para mí de la siguiente manera y luego volver a establecer el tableHeaderView como antes.

 [self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloat height = [self.innerHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + self.innerHeaderView.frame.origin.y; // adding the origin because innerHeaderView starts partway down headerView. CGRect headerFrame = self.headerView.frame; headerFrame.size.height = height; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView:self.headerView]; 

Editar 2: Como han notado otros, la solución publicada en Editar 1 no parece funcionar en viewDidLoad. Sin embargo, parece funcionar en viewWillLayoutSubviews. Ejemplo de código a continuación:

 // Note 1: The variable names below don't match the variables above - this is intended to be a simplified "final" answer. // Note 2: _headerView was previously assigned to tableViewHeader (in loadView in my case since I now do everything progtwigtically). // Note 3: autoLayout code can be setup progtwigtically in updateViewConstraints. - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [_headerWrapper setNeedsLayout]; [_headerWrapper layoutIfNeeded]; CGFloat height = [_headerWrapper systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; CGRect headerFrame = _headerWrapper.frame; headerFrame.size.height = height; _headerWrapper.frame = headerFrame; _tableView.tableHeaderView = _headerWrapper; } 

UIView systemLayoutSizeFittingSize: usar el método UIView systemLayoutSizeFittingSize: para obtener el tamaño límite mínimo de su vista de encabezado.

Proporciono más discusión sobre el uso de esta API en este Q / A:

¿Cómo cambiar el tamaño de la supervista para que se ajuste a todas las subvistas con autolayout?

Realmente luché con este y poner la configuración en viewDidLoad no funcionó para mí ya que el frame no está en viewDidLoad, también terminé con toneladas de advertencias desordenadas donde la altura del encabezado auto encapsulado de la cabecera se estaba reduciendo a 0 . Solo noté el problema en el iPad al presentar un tableView en una presentación de formulario.

Lo que me solucionó el problema fue configurar tableViewHeader en viewWillLayoutSubviews en lugar de en viewDidLoad.

 func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() if tableView.tableViewHeaderView == nil { let header: MyHeaderView = MyHeaderView.createHeaderView() header.setNeedsUpdateConstraints() header.updateConstraintsIfNeeded() header.frame = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGFloat.max) var newFrame = header.frame header.setNeedsLayout() header.layoutIfNeeded() let newSize = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) newFrame.size.height = newSize.height header.frame = newFrame self.tableView.tableHeaderView = header } } 

He encontrado una forma elegante de utilizar el diseño automático para cambiar el tamaño de los encabezados de las tablas, con y sin animación.

Simplemente agregue esto a su Controlador de Vista.

 func sizeHeaderToFit(tableView: UITableView) { if let headerView = tableView.tableHeaderView { let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height var frame = headerView.frame frame.size.height = height headerView.frame = frame tableView.tableHeaderView = headerView headerView.setNeedsLayout() headerView.layoutIfNeeded() } } 

Para cambiar el tamaño de acuerdo con una etiqueta que cambia dinámicamente:

 @IBAction func addMoreText(sender: AnyObject) { self.label.text = self.label.text! + "\nThis header can dynamically resize according to its contents." } override func viewDidLayoutSubviews() { // viewDidLayoutSubviews is called when labels change. super.viewDidLayoutSubviews() sizeHeaderToFit(tableView) } 

Para animar un cambio de tamaño de acuerdo con los cambios en una restricción:

 @IBOutlet weak var makeThisTallerHeight: NSLayoutConstraint! @IBAction func makeThisTaller(sender: AnyObject) { UIView.animateWithDuration(0.3) { self.tableView.beginUpdates() self.makeThisTallerHeight.constant += 20 self.sizeHeaderToFit(self.tableView) self.tableView.endUpdates() } } 

Vea el proyecto AutoResizingHeader para ver esto en acción. https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader

Su solución usando systemLayoutSizeFittingSize: funciona si la vista de encabezado se actualiza una vez en cada aspecto de vista. En mi caso, la vista del encabezado se actualizó varias veces para reflejar los cambios de estado. Pero systemLayoutSizeFittingSize: siempre informó el mismo tamaño. Es decir, el tamaño correspondiente a la primera actualización.

Para obtener systemLayoutSizeFittingSize: para devolver el tamaño correcto después de cada actualización, primero tuve que eliminar la vista del encabezado de la tabla antes de actualizarla y volver a agregarla:

 self.listTableView.tableHeaderView = nil; [self.headerView removeFromSuperview]; 

Esto funcionó para mí en ios10 y Xcode 8

 func layoutTableHeaderView() { guard let headerView = tableView.tableHeaderView else { return } headerView.translatesAutoresizingMaskIntoConstraints = false let headerWidth = headerView.bounds.size.width; let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView]) headerView.addConstraints(temporaryWidthConstraints) headerView.setNeedsLayout() headerView.layoutIfNeeded() let headerSize = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) let height = headerSize.height var frame = headerView.frame frame.size.height = height headerView.frame = frame self.tableView.tableHeaderView = headerView headerView.removeConstraints(temporaryWidthConstraints) headerView.translatesAutoresizingMaskIntoConstraints = true } 

Esta solución cambia el tamaño de tableHeaderView y evita el bucle infinito en el método viewDidLayoutSubviews() que estaba teniendo con algunas de las otras respuestas aquí:

 override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if let headerView = tableView.tableHeaderView { let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height var headerFrame = headerView.frame // comparison necessary to avoid infinite loop if height != headerFrame.size.height { headerFrame.size.height = height headerView.frame = headerFrame tableView.tableHeaderView = headerView } } } 

Véase también esta publicación: https://stackoverflow.com/a/34689293/1245231

En mi caso, viewDidLayoutSubviews funcionó mejor. viewWillLayoutSubviews hace que viewWillLayoutSubviews líneas blancas de un tableView . También agregué comprobando si mi objeto headerView ya existe.

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; if ( ! self.userHeaderView ) { // Setup HeaderView self.userHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"SSUserHeaderView" owner:self options:nil] objectAtIndex:0]; [self.userHeaderView setNeedsLayout]; [self.userHeaderView layoutIfNeeded]; CGFloat height = [self.userHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; CGRect headerFrame = self.userHeaderView.frame; headerFrame.size.height = height; self.userHeaderView.frame = headerFrame; self.tableView.tableHeaderView = self.userHeaderView; // Update HeaderView with data [self.userHeaderView updateWithProfileData]; } } 

Es bastante posible utilizar UIView genérico basado en UIView con cualquier estructura de subvista interna AL como tableHeaderView .

¡Lo único que se necesita es establecer una tableFooterView antes!

Deje self.headerView es una UIView basada en UIView .

 - (void)viewDidLoad { ........................ self.tableView.tableFooterView = [UIView new]; [self.headerView layoutIfNeeded]; // to set initial size self.tableView.tableHeaderView = self.headerView; [self.tableView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor].active = YES; [self.tableView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES; [self.tableView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor].active = YES; // and the key constraint [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES; } 

Si self.headerView cambia de altura bajo la rotación de UI, hay que implementar

 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; [coordinator animateAlongsideTransition: ^(id context) { // needed to resize header height self.tableView.tableHeaderView = self.headerView; } completion: NULL]; } 

Uno puede usar la categoría ObjC para este propósito

 @interface UITableView (AMHeaderView) - (void)am_insertHeaderView:(UIView *)headerView; @end @implementation UITableView (AMHeaderView) - (void)am_insertHeaderView:(UIView *)headerView { NSAssert(self.tableFooterView, @"Need to define tableFooterView first!"); [headerView layoutIfNeeded]; self.tableHeaderView = headerView; [self.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor].active = YES; [self.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES; [self.topAnchor constraintEqualToAnchor:headerView.topAnchor].active = YES; [self.tableFooterView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES; } @end 

Y también la extensión Swift

 extension UITableView { func am_insertHeaderView2(_ headerView: UIView) { assert(tableFooterView != nil, "Need to define tableFooterView first!") headerView.layoutIfNeeded() tableHeaderView = headerView leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true tableFooterView?.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true } }