É possível usar AutoLayout com tableHeaderView do UITableView?

97

Desde que descobri AutoLayoutque o uso em todos os lugares, agora estou tentando usá-lo com umtableHeaderView .

Eu fiz uma subclassde UIViewtudo adicionado (etiquetas etc ...) eu queria com suas limitações, então eu adicionei este CustomViewao UITableView'tableHeaderView .

Tudo funciona bem, exceto o UITableViewsempre exibido acima do CustomView, acima, quero dizer que CustomViewestá abaixo doUITableView por isso não pode ser visto!

Parece que não importa o que eu faça, o heightde UITableView' tableHeaderViewé sempre 0 (assim como a largura, x e y).

Minha pergunta: é possível fazer isso sem definir o quadro manualmente ?

EDIT: O CustomView' subviewque estou usando tem estas restrições:

_title = [[UILabel alloc]init];
_title.text = @"Title";
[self addSubview:_title];
[_title keep:[KeepTopInset rules:@[[KeepEqual must:5]]]]; // title has to stay at least 5 away from the supperview Top
[_title keep:[KeepRightInset rules:@[[KeepMin must:5]]]];
[_title keep:[KeepLeftInset rules:@[[KeepMin must:5]]]];
[_title keep:[KeepBottomInset rules:@[[KeepMin must:5]]]];

Estou usando uma biblioteca útil 'KeepLayout' porque escrever restrições manualmente leva uma eternidade e muitas linhas para uma única restrição, mas os métodos são autoexplicativos.

E o UITableViewtem estas restrições:

_tableView = [[UITableView alloc]init];
_tableView.translatesAutoresizingMaskIntoConstraints = NO;
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.backgroundColor = [UIColor clearColor];
[self.view addSubview:_tableView];
[_tableView keep:[KeepTopInset rules:@[[KeepEqual must:0]]]];// These 4 constraints make the UITableView stays 0 away from the superview top left right and bottom.
[_tableView keep:[KeepLeftInset rules:@[[KeepEqual must:0]]]];
[_tableView keep:[KeepRightInset rules:@[[KeepEqual must:0]]]];
[_tableView keep:[KeepBottomInset rules:@[[KeepEqual must:0]]]];

_detailsView = [[CustomView alloc]init];
_tableView.tableHeaderView = _detailsView;

Não sei se tenho que definir algumas restrições diretamente no CustomView, acho que a altura do CustomView é determinada pelas restrições no UILabel"título" nele.

EDIT 2: Após outra investigação, parece que a altura e a largura do CustomView estão calculadas corretamente, mas o topo do CustomView ainda está no mesmo nível que o topo do UITableView e eles se movem juntos quando eu rolar.

É um segredo
fonte
Sim, é possível. Você pode mostrar o código que está usando? É difícil aconselhar sem saber quais restrições você configurou na visualização do cabeçalho.
jrturton
Uma maneira fácil de fazer isso é adicionar essa visualização em IB à tableView ... basta criar a visualização na mesma cena que contém a tableview e arrastá- la para a tabela.
Mariam K.
Estou tentando evitar o IB o máximo que posso, até agora não tive que usá-lo, se não consigo fazer funcionar, vou tentar com o IB
ItsASecret
1
A Apple aconselha os desenvolvedores a usarem IB sempre que possível quando se trata de autolayout. Realmente ajuda a evitar muitos problemas de inconsistência.
Mariam K.
A verdadeira solução autolayout completa está aqui
malex

Respostas:

134

Eu perguntei e respondi uma pergunta semelhante aqui . Em resumo, adiciono o cabeçalho uma vez e o utilizo para encontrar a altura necessária. Essa altura pode então ser aplicada ao cabeçalho, e o cabeçalho é definido uma segunda vez para refletir a alteração.

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.header = [[SCAMessageView alloc] init];
    self.header.titleLabel.text = @"Warning";
    self.header.subtitleLabel.text = @"This is a message with enough text to span multiple lines. This text is set at runtime and might be short or long.";

    //set the tableHeaderView so that the required height can be determined
    self.tableView.tableHeaderView = self.header;
    [self.header setNeedsLayout];
    [self.header layoutIfNeeded];
    CGFloat height = [self.header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    //update the header's frame and set it again
    CGRect headerFrame = self.header.frame;
    headerFrame.size.height = height;
    self.header.frame = headerFrame;
    self.tableView.tableHeaderView = self.header;
}

Se você tiver rótulos com várias linhas, isso também depende da configuração da visualização personalizada de preferredMaxLayoutWidth de cada rótulo:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);
    self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame);
}

ou talvez de forma mais geral:

override func layoutSubviews() {
    super.layoutSubviews()  
    for view in subviews {
        guard let label = view as? UILabel where label.numberOfLines == 0 else { continue }
        label.preferredMaxLayoutWidth = CGRectGetWidth(label.frame)
    }
}

Atualização de janeiro de 2015

Infelizmente, isso ainda parece necessário. Aqui está uma versão rápida do processo de layout:

tableView.tableHeaderView = header
header.setNeedsLayout()
header.layoutIfNeeded()
header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
tableView.tableHeaderView = header

Achei útil mover isso para uma extensão no UITableView:

extension UITableView {
    //set the tableHeaderView so that the required height can be determined, update the header's frame and set it again
    func setAndLayoutTableHeaderView(header: UIView) {
        self.tableHeaderView = header
        header.setNeedsLayout()
        header.layoutIfNeeded()
        header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
        self.tableHeaderView = header
    }
}

Uso:

let header = SCAMessageView()
header.titleLabel.text = "Warning"
header.subtitleLabel.text = "Warning message here."
tableView.setAndLayoutTableHeaderView(header)
Ben Packard
fonte
8
Uma alternativa ao uso preferredMaxLayoutWidthé adicionar uma restrição de largura (igual à largura da visualização da tabela) na visualização do cabeçalho antes de usar systemLayoutSizeFittingSize:.
Benjohn
2
NOTA: se você perceber que o cabeçalho está acima das primeiras células, você se esqueceu de redefinir a propriedade do cabeçalho paraself.tableView.tableHeaderView
Laszlo
7
Freqüentemente, fico surpreso como pode ser complicado fazer algo completamente trivial como isso.
TylerJames
5
NOTA: Se você precisa obter a largura exata como tableView, você deve obter a altura com a prioridade horizontal necessárialet height = header.systemLayoutSizeFittingSize(CGSizeMake(CGRectGetWidth(self.bounds), 0), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel).height
JakubKnejzlik
3
Just Use header.setNeedsLayout() header.layoutIfNeeded() header.frame.size = header.systemLayoutSizeFitting(UILayoutFittingCompressedSize) self.tableHeaderView = headerfuncionaria no iOS 10.2
Kesong Xie
24

Não consegui adicionar uma visualização de cabeçalho usando restrições (no código). Se eu atribuir à minha visualização uma restrição de largura e / ou altura, recebo uma falha com a mensagem que diz:

 "terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super."

Quando adiciono uma visualização no storyboard à minha visualização de tabela, ela não mostra restrições e funciona bem como uma visualização de cabeçalho, então acho que o posicionamento da visualização de cabeçalho não é feito usando restrições. Não parece se comportar como uma visão normal a esse respeito.

A largura é automaticamente a largura da visualização da mesa, a única coisa que você precisa definir é a altura - os valores de origem são ignorados, então não importa o que você coloque para eles. Por exemplo, funcionou bem (assim como 0,0,0,80 para o reto):

UIView *headerview = [[UIView alloc] initWithFrame:CGRectMake(1000,1000, 0, 80)];
headerview.backgroundColor = [UIColor yellowColor];
self.tableView.tableHeaderView = headerview;
rdelmar
fonte
Eu tive essa exceção também, mas adicionar uma categoria ao UITableView corrigiu isso, encontrei-a nessa resposta: stackoverflow.com/questions/12610783/…
ItsASecret
Ainda tentarei o que você sugere, mas amanhã de manhã, são 1h34. Vou para a cama, muito obrigado por me atender! (Mas eu realmente quero não especificar uma altura, gostaria que fosse calculada pelas restrições que configurei no rótulo no CustomView)
ItsASecret
Eu tentei e sim a configuração da moldura funciona, mas estava procurando uma maneira de evitar a configuração da moldura, continuarei procurando e se não encontrar mais nada aceitarei sua resposta
ItsASecret
1
Recebo esta exceção (atualmente testando 7.1) se a exibição de cabeçalho adicionada tiver translatesAutoresizingMaskIntoConstraints = NO. Ativar a tradução evita o erro - suspeito que a UITableViewpartir do 7.1 não tenta fazer o layout automático de sua exibição de cabeçalho e deseja algo com o quadro predefinido.
Benjohn
16

Eu vi muitos métodos aqui fazendo muitas coisas desnecessárias, mas você não precisa de muito para usar o layout automático na visualização do cabeçalho. Você só precisa criar seu arquivo xib, colocar suas restrições e instanciá-lo assim:

func loadHeaderView () {
        guard let headerView = Bundle.main.loadNibNamed("CourseSearchHeader", owner: self, options: nil)?[0] as? UIView else {
            return
        }
        headerView.autoresizingMask = .flexibleWidth
        headerView.translatesAutoresizingMaskIntoConstraints = true
        tableView.tableHeaderView = headerView
    }
Ramon Vasconcelos
fonte
Isso também funcionou para nós no iOS 11 com um cabeçalho de altura dinâmico com rótulos de várias linhas.
Ben Scheirman,
1
Você também pode remover a FlexibleHeight-Autoresizing-Option no IB, é claro.
d4Rk
Tenho tentado definir a altura do meu tableFooterView (por meio de um xib / nib) e não estou tendo sucesso ao definir o frame, height, layoutIfNeeded (), etc. Mas esta solução finalmente permitiu que eu definisse.
vikzilla
Não se esqueça de definir a restrição de altura para a visualização inteira em um arquivo xib.
Denis Kutlubaev
6

Outra solução é despachar a criação da visualização do cabeçalho para a próxima chamada de thread principal:

- (void)viewDidLoad {
    [super viewDidLoad];

    // ....

    dispatch_async(dispatch_get_main_queue(), ^{
        _profileView = [[MyView alloc] initWithNib:@"MyView.xib"];
        self.tableView.tableHeaderView = self.profileView;
    });
}

Nota: corrige o bug quando a visualização carregada tem uma altura fixa. Não tentei quando a altura do cabeçalho depende apenas de seu conteúdo.

EDITAR:

Você pode encontrar uma solução mais limpa para este problema implementando esta função e chamando-a emviewDidLayoutSubviews

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    [self sizeHeaderToFit];
}
Martin
fonte
1
@TussLaszlo tableHeaderViewsão meio bugs com autolayout. Existem algumas soluções alternativas, como esta. Mas desde que eu escrevi isso, eu encontrei uma solução melhor e mais limpo aqui stackoverflow.com/a/21099430/127493 chamando seu - (void)sizeHeaderToFitnoviewDidLayoutSubviews
Martin
4

Código:

  extension UITableView {

          func sizeHeaderToFit(preferredWidth: CGFloat) {
            guard let headerView = self.tableHeaderView else {
              return
            }

            headerView.translatesAutoresizingMaskIntoConstraints = false
            let layout = NSLayoutConstraint(
              item: headerView,
              attribute: .Width,
              relatedBy: .Equal,
              toItem: nil,
              attribute:
              .NotAnAttribute,
              multiplier: 1,
              constant: preferredWidth)

            headerView.addConstraint(layout)

            let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
            headerView.frame = CGRectMake(0, 0, preferredWidth, height)

            headerView.removeConstraint(layout)
            headerView.translatesAutoresizingMaskIntoConstraints = true

            self.tableHeaderView = headerView
          }
  }
Phil
fonte
Ele funciona. Dê restrições de layout automático adequadas a todas as subvisualizações do tableheaderview Se você perder uma única restrição, ela não funcionará.
abhimuralidharan
4

Esta solução estendida http://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/ para visualização do rodapé da tabela:

@interface AutolayoutTableView : UITableView

@end

@implementation AutolayoutTableView

- (void)layoutSubviews {
    [super layoutSubviews];

    // Dynamic sizing for the header view
    if (self.tableHeaderView) {
        CGFloat height = [self.tableHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect headerFrame = self.tableHeaderView.frame;

        // If we don't have this check, viewDidLayoutSubviews() will get
        // repeatedly, causing the app to hang.
        if (height != headerFrame.size.height) {
            headerFrame.size.height = height;
            self.tableHeaderView.frame = headerFrame;
            self.tableHeaderView = self.tableHeaderView;
        }

        [self.tableHeaderView layoutIfNeeded];
    }

    // Dynamic sizing for the footer view
    if (self.tableFooterView) {
        CGFloat height = [self.tableFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect footerFrame = self.tableFooterView.frame;

        // If we don't have this check, viewDidLayoutSubviews() will get
        // repeatedly, causing the app to hang.
        if (height != footerFrame.size.height) {
            footerFrame.size.height = height;
            self.tableFooterView.frame = footerFrame;
            self.tableFooterView = self.tableFooterView;
        }

        self.tableFooterView.transform = CGAffineTransformMakeTranslation(0, self.contentSize.height - footerFrame.size.height);
        [self.tableFooterView layoutIfNeeded];
    }
}

@end
k06a
fonte
Passei um dia ontem tentando fazer o tableHeader redimensionar / fazer o layout automaticamente. Esta solução funciona para mim. Muitíssimo obrigado.
docchang
Oi! Você poderia explicar a self.tableFooterView.transformparte? Por que isso é necessário?
mrvn
A transformação @mrvn é usada para mover o rodapé para a parte inferior de tableView.
k06a
3

Você pode obter o autolayout para fornecer um tamanho usando o método systemLayoutSizeFittingSize .

Você pode então usar isso para criar o quadro de seu aplicativo. Essa técnica funciona sempre que você precisa saber o tamanho de uma visualização que usa autolayout internamente.

O código em swift parece

//Create the view
let tableHeaderView = CustomTableHeaderView()

//Set the content
tableHeaderView.textLabel.text = @"Hello world"

//Ask auto layout for the smallest size that fits my constraints    
let size = tableHeaderView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

//Create a frame    
tableHeaderView.frame = CGRect(origin: CGPoint.zeroPoint, size: size)

//Set the view as the header    
self.tableView.tableHeaderView = self.tableHeaderView

Ou em Objective-C

//Create the view
CustomTableHeaderView *header = [[CustomTableHeaderView alloc] initWithFrame:CGRectZero];

//Set the content
header.textLabel.text = @"Hello world";

//Ask auto layout for the smallest size that fits my constraints
CGSize size = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

//Create a frame
header.frame = CGRectMake(0,0,size.width,size.height);

//Set the view as the header  
self.tableView.tableHeaderView = header

Também deve ser observado que, nesta instância específica, substituir o requireConstraintBasedLayout em sua subclasse, resulta em uma passagem de layout sendo executada; no entanto, os resultados dessa passagem de layout são ignorados e o quadro do sistema definido para a largura de tableView e altura 0.

Jonathan
fonte
3

O seguinte funcionou para mim.

  1. Use um simples e velho UIView como a visualização do cabeçalho.
  2. Adicione subvisualizações a isso UIView
  3. Use autolayout nas subvisualizações

O principal benefício que vejo é limitar os cálculos do quadro. A Apple realmente deveria atualizarUITableView a API para tornar isso mais fácil.

Exemplo de uso do SnapKit:

let layoutView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 60))
layoutView.backgroundColor = tableView.backgroundColor
tableView.tableHeaderView = layoutView

let label = UILabel()
layoutView.addSubview(label)
label.text = "I'm the view you really care about"
label.snp_makeConstraints { make in
    make.edges.equalTo(EdgeInsets(top: 10, left: 15, bottom: -5, right: -15))
}
David Nix
fonte
3

Coisas estranhas acontecem. systemLayoutSizeFittingSize funciona muito bem para iOS9, mas não para iOS 8 no meu caso. Portanto, esse problema é resolvido com bastante facilidade. Basta obter o link para a visualização inferior no cabeçalho e em viewDidLayoutSubviews após a super chamada atualizar os limites da visualização do cabeçalho inserindo a altura como CGRectGetMaxY (yourview.frame) + preenchimento

UPD: A solução mais fácil de todas : Então, na visualização do cabeçalho, coloque a subvisualização e fixe-a à esquerda , direita , topo . Nessa subvisualização, coloque suas subvisualizações com restrições de altura automática. Depois disso, dê todo o trabalho para o autolayout (nenhum cálculo necessário)

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    CGFloat height = CGRectGetMaxY(self.tableView.tableHeaderView.subviews.firstObject.frame);
    self.tableView.tableHeaderView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height);
    self.tableView.tableHeaderView = self.tableView.tableHeaderView;
}

Como resultado, a subvisualização está se expandindo / diminuindo como deveria, no final ela chama viewDidLayoutSubviews. No momento, sabemos o tamanho real da visualização, então defina headerView height e atualize-a reatribuindo. Funciona como um encanto!

Também funciona para visualização de rodapé.

HotJard
fonte
1
Isso faz um loop para mim no iOS 10.
Simon,
3

Atualizado para Swift 4.2

extension UITableView {

    var autolayoutTableViewHeader: UIView? {
        set {
            self.tableHeaderView = newValue
            guard let header = newValue else { return }
            header.setNeedsLayout()
            header.layoutIfNeeded()
            header.frame.size = 
            header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
            self.tableHeaderView = header
        }
        get {
            return self.tableHeaderView
        }
    }
}
Caleb Friden
fonte
2

você pode adicionar restrição de localização superior + horizontal entre o cabeçalho e tableview, para colocá-lo, corretamente (se o próprio cabeçalho contiver todas as restrições de layout internas necessárias para ter um quadro correto)

no método tableViewController viewDidLoad

    headerView.translatesAutoresizingMaskIntoConstraints = false

    tableView.tableHeaderView = headerView

    headerView.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true
    headerView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true
    headerView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true
GreatWiz
fonte
1

Minha visualização do cabeçalho da tabela é uma subclasse UIView - eu criei um único UIView contentView dentro do inicializador, com seus limites iguais aos do quadro da visualização do cabeçalho da tabela e adicionei todos os meus objetos como uma subvisão desse.

Em seguida, adicione as restrições para seus objetos dentro do layoutSubviewsmétodo da visualização do cabeçalho da tabela em vez de dentro do inicializador. Isso resolveu o acidente.

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:CGRectMake(0, 0, 0, 44.0)];
    if (self) {
        UIView *contentView = [[UIView alloc] initWithFrame:self.bounds];
        contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth;

        // add other objects as subviews of content view

    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    // remake constraints here
}
Ryan
fonte
1

Meu AutoLayout está funcionando muito bem:

CGSize headerSize = [headerView systemLayoutSizeFittingSize:CGSizeMake(CGRectGetWidth([UIScreen mainScreen].bounds), 0) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
headerView.frame = CGRectMake(0, 0, headerSize.width, headerSize.height);
self.tableView.tableHeaderView = headerView;
RomanV
fonte
Não fiz exatamente isso, mas você me deu uma boa ideia - remover headerView, redefinir seu frame e adicioná-lo de volta.
dinesharjani
1

Na maioria dos casos, a melhor solução é simplesmente não lutar contra a estrutura e adotar máscaras de redimensionamento automático:

// embrace autoresizing masks and let the framework add the constraints for you
headerView.translatesAutoresizingMaskIntoConstraints = true
headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

// figure out what's the best size based on the table view width
let width = self.tableView.frame.width
let targetSize = headerView.systemLayoutSizeFitting(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
headerView.frame.size = targetSize
self.tableView.tableHeaderView = headerView

Ao usar máscaras de redimensionamento automático, você informa ao framework como sua visualização deve mudar de tamanho quando a visualização muda de tamanho. Mas essa mudança é baseada no quadro inicial que você definiu.

pfandrade
fonte
0

Eu sei que este é um post antigo, mas depois de passar por todos os posts do SO sobre isso e passar uma tarde inteira brincando com isso, eu finalmente encontrei uma solução limpa e muito simples

Em primeiro lugar, minha hierarquia de visualização é assim:

  1. Vista de mesa
    1. Visão tableHeaderView
      1. Visão com uma saída chamada headerView

Agora dentro da View (No.3), eu configurei todas as restrições como faria normalmente, incluindo o espaço inferior do container. Isso fará com que o contêiner (ou seja, 3.View, ou seja, headerView), se dimensione com base em suas subvisualizações e suas restrições.

Depois disso, eu defino as restrições entre 3. Viewe 2. Viewpara estas:

  1. Espaço superior para o contêiner: 0
  2. Espaço inicial para o contêiner: 0
  3. Espaço à direita para o contêiner: 0

Observe que eu omito intencionalmente o espaço inferior intencionalmente.

Depois que tudo isso é feito no storyboard, tudo o que resta fazer é colar essas três linhas de códigos:

if (self.headerView.frame.size.height != self.tableView.tableHeaderView.frame.size.height) {
    UIView *header = self.tableView.tableHeaderView;
    CGRect frame = self.tableView.tableHeaderView.frame;
    frame.size.height = self.headerView.frame.size.height + frame.origin.y;
    header.frame = frame;
    self.tableView.tableHeaderView = header;
}
Marc-Alexandre Bérubé
fonte
0

Dicas: Se você usar o método setAndLayoutTableHeaderView, deverá atualizar o quadro de subvisualizações, portanto, nesta situação, o UILabel's preferredMaxLayoutWidth deve chamar antes de systemLayoutSizeFittingSize ser chamado, não chame em layoutSubview.

show de código

user1511613
fonte
0

Compartilhe minha abordagem.

UITableView+XXXAdditions.m

- (void)xxx_setTableHeaderView:(UIView *)tableHeaderView layoutBlock:(void(^)(__kindof UIView *tableHeaderView, CGFloat *containerViewHeight))layoutBlock {
      CGFloat containerViewHeight = 0;
      UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
      [backgroundView addSubview:tableHeaderView];
      layoutBlock(tableHeaderView, &containerViewHeight);

      backgroundView.frame = CGRectMake(0, 0, 0, containerViewHeight);

      self.tableHeaderView = backgroundView;
}

Uso.

[self.tableView xxx_setTableHeaderView:myView layoutBlock:^(__kindof UIView * _Nonnull tableHeaderView, CGFloat *containerViewHeight) {
    *containerViewHeight = 170;

    [tableHeaderView mas_makeConstraints:^(MASConstraintMaker *make) {
      make.top.equalTo(@20);
      make.centerX.equalTo(@0);
      make.size.mas_equalTo(CGSizeMake(130, 130));
    }];
  }];
Vincent Sit
fonte
0

No meu caso, o método com systemLayoutSizeFittingSize por algum motivo não funcionou. O que funcionou para mim foi uma modificação da solução postada pelo HotJard (a solução original também não funcionou no meu caso no iOS 8). O que eu precisava fazer é, na visualização do cabeçalho, colocar uma subvisualização e fixá-la à esquerda, direita, parte superior (não fixe na parte inferior). Coloque tudo usando autolayout nessa subvisão e no código faça o seguinte:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    [self resizeHeaderToFitSubview];
}

- (void)resizeHeaderToFitSubview
{
    UIView *header = self.tableView.tableHeaderView;
    [header setNeedsLayout];
    [header layoutIfNeeded];
    CGFloat height = CGRectGetHeight(header.subviews.firstObject.bounds);
    header.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height);
    self.tableView.tableHeaderView = nil;
    self.tableView.tableHeaderView = header;
}
Leszek Szary
fonte
0

Um post antigo. Mas um bom post. Aqui estão meus 2 centavos.

Em primeiro lugar, certifique-se de que sua visualização de cabeçalho tenha suas restrições organizadas de forma que possa suportar seu próprio tamanho de conteúdo intrínseco. Em seguida, faça o seguinte.

//ViewDidLoad
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.configure(title: "Some Text A")

//Somewhere else
headerView.update(title: "Some Text B)

private var widthConstrained = false

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if widthConstrained == false {
        widthConstrained = true
        tableView.addConstraint(NSLayoutConstraint(item: headerView, attribute: .width, relatedBy: .equal, toItem: tableView, attribute: .width, multiplier: 1, constant: 0))
        headerView.layoutIfNeeded()
        tableView.layoutIfNeeded()
    }
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { (context) in
        self.headerView.layoutIfNeeded()
        self.tableView.layoutIfNeeded()
    }, completion: nil)
}

fonte
0

Consegui consegui-lo com a seguinte abordagem (isso funciona para o rodapé da mesma maneira).

Primeiro, você precisará de uma pequena UITableViewextensão:

Swift 3

extension UITableView {
    fileprivate func adjustHeaderHeight() {
        if let header = self.tableHeaderView {
            adjustFrame(header)
        }
    }

    private func adjustFrame(_ view: UIView) {
        view.frame.size.height = calculatedViewHeight(view)
    }

    fileprivate func calculatedHeightForHeader() -> CGFloat {
        if let header = self.tableHeaderView {
            return calculatedViewHeight(header)
        }
        return 0.0
    }

    private func calculatedViewHeight(_ view: UIView) -> CGFloat {
        view.setNeedsLayout()
        let height = view.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        return height
    }
}

Em sua implementação de classe de controlador de visualização:

// this is a UIView subclass with autolayout
private var headerView = MyHeaderView()

override func loadView() {
    super.loadView()
    // ...
    self.tableView.tableHeaderView = headerView
    self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension
    // ...
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    // this is to prevent recursive layout calls
    let requiredHeaderHeight = self.tableView.calculatedHeightForHeader()
    if self.headerView.frame.height != requiredHeaderHeight {
        self.tableView.adjustHeaderHeight()
    }
}

Observações sobre a UIViewimplementação da subvisão de um cabeçalho :

  1. Você deve ter 100% de certeza de que a visualização do cabeçalho possui a configuração correta de layout automático. Eu recomendaria começar com uma visualização de cabeçalho simples com apenas uma restrição de altura e experimentar a configuração acima.

  2. Substituir requiresConstraintBasedLayoute retornar true:

.

class MyHeaderView: UIView {
   // ...
   override static var requiresConstraintBasedLayout : Bool {
       return true
   }
   // ...
}
Yevhen Dubinin
fonte
0

Para usuários Xamarin:

public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();

    TableviewHeader.SetNeedsLayout();
    TableviewHeader.LayoutIfNeeded();

    var height = TableviewHeader.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize).Height;
    var frame = TableviewHeader.Frame;
    frame.Height = height;
    TableviewHeader.Frame = frame;
}

Supondo que você nomeou a visualização do cabeçalho de sua tableview como TableviewHeader

Gustavo Baiocchi Costa
fonte
0

Aqui está como você pode fazer em seu UIViewController

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if headerView.frame.size.height == 0 {
      headerView.label.preferredMaxLayoutWidth = view.bounds.size.width - 20
      let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height

      headerView.frame.size = CGSize(width: tableView.bounds.size.width, height: height)
    }
  }
onmyway133
fonte
0

Qualquer restrição baseada em UIViewpode ser uma boa tableHeaderView.

É necessário definir um tableFooterViewantes e, em seguida, impor uma restrição adicional à direita tableFooterViewe tableHeaderView.

- (void)viewDidLoad {

    ........................
    // let self.headerView is some constraint-based UIView
    self.tableView.tableFooterView = [UIView new];
    [self.headerView layoutIfNeeded];
    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;
    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;

}

Todos os detalhes e trechos de código podem ser encontrados aqui

malex
fonte
0

Eu descobri uma solução alternativa. envolva sua visão de cabeçalho autolayout wrriten xib em um wrapper uiview vazio e atribua a visão de cabeçalho à propriedade tableViewHeader de tableView.

    UIView *headerWrapper = [[UIView alloc] init];
    AXLHomeDriverHeaderView *headerView = [AXLHomeDriverHeaderView loadViewFromNib];
    [headerWrapper addSubview:headerView];
    [headerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(headerWrapper);
    }];
    self.tableView.tableHeaderView = headerView;
Tounaobun
fonte
0

Aqui está o que funciona para UITableViewController no ios 12,

Arraste um UIView no TableView acima de todas as células de protótipo para cabeçalho e abaixo de todas as células de protótipo para rodapé. Configure seu cabeçalho e rodapé conforme necessário. Defina todas as restrições necessárias.

Agora use os seguintes métodos de extensão

public static class UITableVIewExtensions
{

    public static void MakeHeaderAutoDimension(this UITableView tableView)
    {
        if (tableView.TableHeaderView is UIView headerView) {
            var size = headerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
            if (headerView.Frame.Size.Height != size.Height) {
                var frame = headerView.Frame;
                frame.Height = size.Height;
                headerView.Frame = frame;
                tableView.TableHeaderView = headerView;
                tableView.LayoutIfNeeded();
            }
        }
    }

    public static void MakeFooterAutoDimension(this UITableView tableView)
    {
        if (tableView.TableFooterView is UIView footerView) {
            var size = footerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
            if (footerView.Frame.Size.Height != size.Height) {
                var frame = footerView.Frame;
                frame.Height = size.Height;
                footerView.Frame = frame;
                tableView.TableFooterView = footerView;
                tableView.LayoutIfNeeded();
            }
        }
    }
}

e chamá-lo em ViewDidLayoutSubviews da subclasse de UITableViewController

public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();

    TableView.MakeHeaderAutoDimension();
    TableView.MakeFooterAutoDimension();
}
Papai bêbado
fonte
0

Eu encontrei o problema de obter largura de 375pt, a única maneira que funcionou para mim é retransmitir o tableView para obter a largura correta. Eu também preferi o AutoLayout em vez de definir o tamanho do quadro.

Esta é a versão que funciona para mim:

Xamarin.iOS

public static void AutoLayoutTableHeaderView(this UITableView tableView, UIView header)
{
    tableView.TableHeaderView = header;
    tableView.SetNeedsLayout();
    tableView.LayoutIfNeeded();
    header.WidthAnchor.ConstraintEqualTo(tableView.Bounds.Width).Active = true;       
    tableView.TableHeaderView = header;
}

Versão Swift (modificada da resposta @Ben Packard)

extension UITableView {
    //set the tableHeaderView so that the required height can be determined, update the header's frame and set it again
    func setAndLayoutTableHeaderView(header: UIView) {
        self.tableHeaderView = header
        self.setNeedsLayout()
        self.layoutIfNeeded()
        header.widthAnchor.widthAnchor.constraint(equalTo: self.bounds.width).isActive = true
        self.tableHeaderView = header
    }
}
Baron Ch'ng
fonte
0

Minha solução é fazer uma nova classe como essa.

class BaseTableHeaderView: UIView {

    func sizeToFitBasedOnConstraints(width: CGFloat = Screen.width) {
        let size = systemLayoutSizeFitting(CGSize(width: width, height: 10000),
                                              withHorizontalFittingPriority: .required,
                                              verticalFittingPriority: .fittingSizeLevel)
        frame = CGRect(origin: .zero, size: size)
    }

    override func willMove(toSuperview newSuperview: UIView?) {
        sizeToFitBasedOnConstraints()
        super.willMove(toSuperview: newSuperview)
    }

}

Para usá-lo, basta adicionar todas as suas subvisualizações a uma instância de BaseTableHeaderViewe anexá-las à sua visão de tabela.

let tableHeaderView = BaseTableHeaderView()
tableHeaderView.addSubview(...)
tableView.tableHeaderView = tableHeaderView

Ele será redimensionado automaticamente com base em suas restrições.

Hesse Huang
fonte
-1

A resposta aceita é útil apenas para tabelas com uma única seção. Para várias seções, UITableViewcertifique-se de que seu cabeçalho herda deUITableViewHeaderFooterView e você ficará bem.

Como alternativa, basta incorporar seu cabeçalho atual no contentViewde a UITableViewHeaderFooterView. Exatamente como UITableViewCellfunciona.

redent84
fonte
9
a questão tableHeaderViewnão é sobre o cabeçalho da seção.
Rpranata,