ios - ¿Cómo configuro la altura de tableHeaderView(UITableView) con autolayout?
ios7 (8)
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];
}
}
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.
Debido a que la descripción general (por ejemplo, el texto "Información general de la lista aquí.") Tiene contenido dinámico, 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:
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 programatically).
// Note 3: autoLayout code can be setup programatically 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;
}
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<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> 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
}
}
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://.com/a/34689293/1245231
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
}
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
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
}
}
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];
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?