Estou batendo minha cabeça contra a parede com isso nas últimas 3 ou 4 horas e não consigo descobrir. Eu tenho um UIViewController com um UITableView de tela inteira dentro dele (há algumas outras coisas na tela, é por isso que não posso usar um UITableViewController) e quero que meu tableHeaderView redimensione com autolayout. Desnecessário dizer que não está cooperando.
Veja a imagem abaixo.
Como o overviewLabel (por exemplo, o texto "Listar informações gerais aqui.") Tem conteúdo dinâmico, estou usando o autolayout para redimensioná-lo e sua visualização. Eu tenho tudo redimensionado muito bem, exceto para o tableHeaderView, que está logo abaixo do Paralax Table View na pesquisa.
A única maneira que encontrei de redimensionar a visualização do cabeçalho é programaticamente, com o seguinte código:
CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = headerFrameHeight;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];
Nesse caso, headerFrameHeight é um cálculo manual da altura de tableViewHeader da seguinte maneira (innerHeaderView é a área em branco ou a segunda "Visualização", headerView é 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);
O cálculo manual funciona, mas é desajeitado e sujeito a erros se as coisas mudarem no futuro. O que eu quero fazer é redimensionar automaticamente o tableHeaderView com base nas restrições fornecidas, como você faria em qualquer outro lugar. Mas pela minha vida, eu não consigo entender.
Existem vários posts no SO sobre isso, mas nenhum é claro e acabou me confundindo mais. Aqui estão alguns:
Realmente não faz sentido alterar a propriedade translatesAutoresizingMaskIntoConstraints para NO, já que isso apenas causa erros para mim e não faz sentido conceitualmente de qualquer maneira.
Qualquer ajuda seria realmente apreciada!
EDIT 1: Graças à sugestão de TomSwift, eu fui capaz de descobrir. Em vez de calcular manualmente a altura da visão geral, posso fazer com que ela seja calculada da seguinte maneira e, em seguida, reconfigure o 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];
Edição 2: como outros notaram, a solução postada na Edição 1 não parece funcionar em viewDidLoad. No entanto, parece funcionar em viewWillLayoutSubviews. Código de exemplo abaixo:
// 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;
}
fonte
setTableHeaderView
não funciona no Xcode6. O problema é que as células são sobrepostas por tableHeaderView. No entanto, funciona no Xcode5Respostas:
Você precisa usar o
UIView systemLayoutSizeFittingSize:
método para obter o tamanho limite mínimo de sua visualização de cabeçalho.Eu forneço uma discussão mais aprofundada sobre o uso desta API neste Q / A:
Como redimensionar superview para caber em todas as subviews com autolayout?
fonte
Eu realmente lutei com este e colocar a configuração em viewDidLoad não funcionou para mim, pois o quadro não está definido em viewDidLoad, também acabei com toneladas de avisos confusos onde a altura do layout automático encapsulado do cabeçalho estava sendo reduzida a 0 Eu só percebi o problema no iPad ao apresentar um tableView em uma apresentação de formulário.
O que resolveu o problema para mim foi definir o tableViewHeader em viewWillLayoutSubviews em vez de em 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 } }
fonte
Eu descobri uma maneira elegante de usar o layout automático para redimensionar os cabeçalhos das tabelas, com e sem animação.
Basta adicionar isso ao seu View Controller.
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 redimensionar de acordo com um rótulo que muda dinamicamente:
@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 um redimensionamento de acordo com as alterações em uma restrição:
@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() } }
Consulte o projeto AutoResizingHeader para ver isso em ação. https://github.com/p-sun/Swift2-iOS9-UI
fonte
@IBOutlet makeThisTaller
e@IBAction fun makeThisTaller
como no exemplo. Além disso, restrinja todos os lados de seu rótulo ao tableViewHeader (por exemplo, superior, inferior, esquerdo e direito).lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.width
onde rótulo é aquele que desejo aumentar de tamanho. Obrigado !Esta solução redimensiona o tableHeaderView e evita o loop infinito no
viewDidLayoutSubviews()
método que estava usando com algumas das outras respostas aqui: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 } } }
Veja também esta postagem: https://stackoverflow.com/a/34689293/1245231
fonte
Sua solução usando systemLayoutSizeFittingSize: funciona se a visualização do cabeçalho for atualizada apenas uma vez em cada aparência da visualização. No meu caso, a visualização do cabeçalho foi atualizada várias vezes para refletir as mudanças de status. Mas systemLayoutSizeFittingSize: sempre relatou o mesmo tamanho. Ou seja, o tamanho correspondente à primeira atualização.
Para obter systemLayoutSizeFittingSize: para retornar o tamanho correto após cada atualização, eu tive que primeiro remover a visualização do cabeçalho da tabela antes de atualizá-la e adicioná-la novamente:
self.listTableView.tableHeaderView = nil; [self.headerView removeFromSuperview];
fonte
Isso funcionou para mim no ios10 e 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 }
fonte
Funciona tanto na visualização do cabeçalho quanto no rodapé, basta substituir o cabeçalho pelo rodapé
func sizeHeaderToFit() { if let headerView = tableView.tableHeaderView { headerView.setNeedsLayout() headerView.layoutIfNeeded() let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height var frame = headerView.frame frame.size.height = height headerView.frame = frame tableView.tableHeaderView = headerView } }
fonte
Para iOS 12 e superior, as etapas a seguir garantirão que o autolayout funcione corretamente na tabela e no cabeçalho.
fonte
No meu caso
viewDidLayoutSubviews
funcionou melhor.viewWillLayoutSubviews
faz com que as linhas brancas detableView
a apareçam. Também adicionei a verificação se meuheaderView
objeto já 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]; } }
fonte
É bem possível usar AutoLayout genérico com base
UIView
em qualquer estrutura de subvisualização interna do AL como umtableHeaderView
.A única coisa que se precisa é definir um
tableFooterView
antes simples !Vamos
self.headerView
alguns baseados em restriçõesUIView
.- (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; }
Se a
self.headerView
altura for alterada sob a rotação da IU, será necessário 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]; }
Pode-se usar a categoria ObjC para esta finalidade
@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
E também a extensão 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 } }
fonte
Esta solução funciona perfeitamente para mim:
https://spin.atomicobject.com/2017/08/11/swift-extending-uitableviewcontroller/
Ele estende o UITableViewController. Mas se você estiver usando apenas um UITableView, ele ainda funcionará, apenas estenda o UITableView ao invés do UITableViewController. Chame os métodos
sizeHeaderToFit()
ousizeFooterToFit()
sempre que houver um evento que altere atableViewHeader
altura.fonte
Copiado desta postagem
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if let headerView = tableView.tableHeaderView { let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).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 } } }
fonte