As alturas das células dinâmicas UITableView corrigem apenas após alguma rolagem


117

Eu tenho um UITableViewcom um personalizado UITableViewCelldefinido em um storyboard usando layout automático. A célula possui várias linhas UILabels.

O UITableViewparece calcular corretamente as alturas das células, mas para as primeiras células essa altura não é dividida corretamente entre os rótulos. Depois de rolar um pouco, tudo funciona conforme o esperado (mesmo as células que estavam inicialmente incorretas).

- (void)viewDidLoad {
    [super viewDidLoad]
    // ...
    self.tableView.rowHeight = UITableViewAutomaticDimension;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
    // ...
    // Set label.text for variable length string.
    return cell;
}

Existe alguma coisa que eu possa estar perdendo, que está fazendo com que o layout automático não consiga fazer seu trabalho nas primeiras vezes?

Criei um projeto de amostra que demonstra esse comportamento.

Projeto de amostra: Vista superior da tabela do projeto de amostra no primeiro carregamento. Projeto de amostra: mesmas células após rolar para baixo e voltar para cima.


1
Eu também estou enfrentando esse problema, mas a resposta abaixo não está funcionando para mim, Qualquer ajuda sobre isso
— Shangari C

1
enfrentando o mesmo problema de adicionar layoutIfNeeded para celular não funciona bem? mais alguma sugestão
— Máx.

1
Ótimo local. Isso ainda é um problema em 2019: /
— Fattie,

Eu descobri o que me ajudou no seguinte link - é uma discussão bastante abrangente sobre células de visualização de tabela de altura variável: stackoverflow.com/a/18746930/826946
— Andy Weinstein

Respostas:


139

Não sei se isso está claramente documentado ou não, mas adicionar [cell layoutIfNeeded]antes de devolver o celular resolve seu problema.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
    NSUInteger n1 = firstLabelWordCount[indexPath.row];
    NSUInteger n2 = secondLabelWordCount[indexPath.row];
    [cell setNumberOfWordsForFirstLabel:n1 secondLabel:n2];

    [cell layoutIfNeeded]; // <- added

    return cell;
}

3
Eu me pergunto, por que é necessário apenas na primeira vez? Funciona tão bem se eu ligar para -layoutIfNeededno do celular -awakeFromNib. Prefiro ligar apenas para layoutIfNeededonde sei por que é necessário.
— blackp

2
Funcionou para mim! E eu adoraria saber por que é necessário!
— Mustafa

Não funcionou se você renderizar a classe de tamanho, que é diferente de qualquer X qualquer
— Andrei Konstantinov

@AndreyKonstantinov Sim, isso não funciona com classe de tamanho, como faço para que funcione com classe de tamanho?
— z22

Funciona sem classe de tamanho, alguma solução com classe de tamanho?
— Yuvrajsinh 02 de

38

Isso funcionou para mim quando outras soluções semelhantes não:

override func didMoveToSuperview() {
    super.didMoveToSuperview()
    layoutIfNeeded()
}

Este parece ser um bug real, já que estou muito familiarizado com o AutoLayout e como usar UITableViewAutomaticDimension, no entanto, ainda ocasionalmente me deparo com esse problema. Estou feliz por finalmente encontrar algo que funciona como uma solução alternativa.


1
Melhor do que a resposta aceita, pois só é chamada quando a célula está sendo carregada do xib em vez de cada exibição de célula. Muito menos linhas de código também.
— Robert Wagstaff,

3
Não se esqueça de ligarsuper.didMoveToSuperview()
— cicerocamargo

@cicerocamargo Apple diz sobre didMoveToSuperview: "A implementação padrão deste método não faz nada."
— Ivan Smetanin,

4
@IvanSmetanin não importa se a implementação padrão não faz nada, você ainda deve chamar o método super, pois ele pode realmente fazer algo no futuro.
— TNguyen

(A propósito, você DEFINITIVAMENTE deve chamar super. Sobre isso. Nunca vi um projeto em que a equipe em geral não subclasse mais de uma célula, então, é claro que você deve ter certeza de pegar todas elas. E como um problema separado, exatamente o que TNguyen disse.)
— Fattie,

22

Adicionando [cell layoutIfNeeded]em cellForRowAtIndexPathnão funciona para células que são inicialmente enrolado out-of-view.

Nem prefaciar com [cell setNeedsLayout].

Você ainda tem que rolar certas células para fora e de volta à vista para que sejam redimensionadas corretamente.

Isso é muito frustrante, já que a maioria dos desenvolvedores possui Dynamic Type, AutoLayout e Self-Sizing Cells funcionando corretamente - exceto neste caso irritante. Este bug afeta todos os meus controladores de visualização de tabela "mais altos".


O mesmo para mim. quando eu rolar pela primeira vez, o tamanho da célula é 44px. se eu rolar para tirar a célula de vista e voltar, ela será dimensionada corretamente. Você achou alguma solução?
— Máximo de

Obrigado @ ashy_32bit isso resolveu para mim também.
— ImpurestClub de

parece ser um bug normal, @Scenario certo? coisas horríveis.
— Fattie

3
Usar em [cell layoutSubviews]vez de layoutIfNeeded pode ser uma solução possível. Consulte stackoverflow.com/a/33515872/1474113
— ypresto

14

Tive a mesma experiência em um de meus projetos.

Por que isso acontece?

Célula projetada no Storyboard com alguma largura para algum dispositivo. Por exemplo, 400px. Por exemplo, sua etiqueta tem a mesma largura. Quando carrega do storyboard, tem largura de 400 px.

Aqui está um problema:

tableView:heightForRowAtIndexPath: chamado antes do layout da célula é subvisualizações.

Portanto, ele calculou a altura do rótulo e da célula com largura de 400 px. Mas você roda em aparelho com tela, por exemplo, 320px. E esta altura calculada automaticamente está incorreta. Só porque o celular layoutSubviewssó acontece depois. tableView:heightForRowAtIndexPath: Mesmo que você configure preferredMaxLayoutWidthseu rótulo manualmente, layoutSubviewsisso não ajuda.

Minha solução:

1) Subclasse UITableViewe substituição dequeueReusableCellWithIdentifier:forIndexPath:. Defina a largura da célula igual à largura da tabela e o layout da célula de força.

- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [super dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
    CGRect cellFrame = cell.frame;
    cellFrame.size.width = self.frame.size.width;
    cell.frame = cellFrame;
    [cell layoutIfNeeded];
    return cell;
}

2) Subclasse UITableViewCell. Defina preferredMaxLayoutWidthmanualmente para seus rótulos em layoutSubviews. Além disso, você precisa do layout manualmente contentView, porque ele não faz o layout automaticamente após a mudança do quadro da célula (não sei por que, mas é)

- (void)layoutSubviews {
    [super layoutSubviews];
    [self.contentView layoutIfNeeded];
    self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}

1
Quando me deparei com esse problema, também precisava garantir que o quadro do tableview estava correto, então chamei [self.tableview layoutIfNeeded] antes que o tableview fosse preenchido (em viewDidLoad).
— GK100

Nunca parei para pensar que tinha a ver com uma largura incorreta. em cell.frame.size.width = tableview.frame.widthseguida cell.layoutIfNeeded(), a cellForRowAtfunção funcionou para mim
— Cam Connor

10

Eu tenho um problema semelhante, no primeiro carregamento, a altura da linha não foi calculada, mas depois de alguma rolagem ou vá para outra tela e volto a esta tela as linhas são calculadas. No primeiro carregamento, meus itens são carregados da internet e no segundo carregamento meus itens são carregados primeiro do Core Data e recarregados da internet e eu percebi que a altura das linhas são calculadas no recarregamento da internet. Portanto, percebi que quando tableView.reloadData () é chamado durante a animação segue (mesmo problema com push e segue segue), a altura da linha não foi calculada. Então, escondi o tableview na inicialização do modo de exibição e coloquei um carregador de atividade para evitar um efeito desagradável para o usuário e chamei tableView.reloadData após 300 ms e agora o problema está resolvido. Acho que é um bug do UIKit, mas essa solução alternativa resolve o problema.

Eu coloquei essas linhas (Swift 3.0) no meu gerenciador de conclusão de carga de item

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute: {
        self.tableView.isHidden = false
        self.loader.stopAnimating()
        self.tableView.reloadData()
    })

Isso explica por que, para algumas pessoas, colocar reloadData em layoutSubviews resolve o problema


Este é o único WA que funcionou para mim também. Exceto que eu não uso isHidden, mas configurei o alfa da tabela para 0 ao adicionar a fonte de dados e, em seguida, para 1 após recarregar.
— PJ_Finnegan

6

nenhuma das soluções acima funcionou para mim, o que funcionou é esta receita de mágica: chame-os nesta ordem:

tableView.reloadData()
tableView.layoutIfNeeded() tableView.beginUpdates() tableView.endUpdates()

meus dados do tableView são preenchidos a partir de um serviço da web, no retorno de chamada da conexão, escrevo as linhas acima.


1
Para o swift 5 isso funcionou até mesmo na visualização de coleção, Deus te abençoe, mano.
— Govani Dhruv Vijaykumar

Para mim, isso retornou a altura e o layout corretos da célula, mas não tinha dados
— Darrow Hartman

Tente setNeedsDisplay () após essas linhas
— JAHelia

5

No meu caso, a última linha do UILabel foi truncada quando a célula foi exibida pela primeira vez. Aconteceu de forma bastante aleatória e a única maneira de dimensionar corretamente era rolar a célula para fora da visualização e trazê-la de volta. Tentei todas as soluções possíveis exibidas até agora (layoutIfNeeded..reloadData), mas nada funcionou para mim. O truque era definir "Autoshrink" para Minimuum Font Scale (0,5 para mim). De uma chance


isso funcionou para mim, sem aplicar nenhuma das outras soluções listadas acima. Ótimo achado.
— mckeejm,

2
Mas isso autoshrinks o texto ... por que alguém iria querer ter tamanhos diferentes de texto em uma exibição de tabela? Parece terrível.
— Lucius Degeer

5

Adicione uma restrição para todo o conteúdo dentro de uma célula personalizada de visualização de tabela, em seguida, estime a altura da linha de visualização de tabela e defina a altura da linha para dimensão automática com uma carga viewdid:

    override func viewDidLoad() {
    super.viewDidLoad()

    tableView.estimatedRowHeight = 70
    tableView.rowHeight = UITableViewAutomaticDimension
}

Para corrigir esse problema de carregamento inicial, aplique o método layoutIfNeeded em uma célula de visualização de tabela personalizada:

class CustomTableViewCell: UITableViewCell {

override func awakeFromNib() {
    super.awakeFromNib()
    self.layoutIfNeeded()
    // Initialization code
}
}

É importante definir estimatedRowHeightum valor> 0 e não UITableViewAutomaticDimension(que é -1), caso contrário, a altura da linha automática não funcionará.
— PJ_Finnegan

5

Tentei a maioria das respostas a esta pergunta e não consegui fazer nenhuma delas funcionar. A única solução funcional que encontrei foi adicionar o seguinte à minha UITableViewControllersubclasse:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    UIView.performWithoutAnimation {
        tableView.beginUpdates()
        tableView.endUpdates()
    }
}

A UIView.performWithoutAnimationchamada é necessária, caso contrário, você verá a animação normal da visualização da tabela conforme o controlador da visualização carrega.


funcionando e definitivamente melhor do que invocar reloadData duas vezes
— Jimmy George Thomas

2
Magia negra. Fazer em viewWillAppearnão funcionou para mim, mas fazê-lo em viewDidAppearsim.
— Martin,

Esta solução 'funcionou' para mim quando implementada no viewDidAppear. Não é, de forma alguma, ideal para a experiência do usuário, pois o conteúdo da tabela muda conforme ocorre o dimensionamento correto.
— richardpiazza

incrível, eu tentei muitos exemplos, mas falhou, você é um demônio
— Shakeel Ahmed

O mesmo que @martin
— AJ Hernandez


3

ligar para cell.layoutIfNeeded()dentro cellForRowAtfuncionou para mim no iOS 10 e no iOS 11, mas não no iOS 9.

para conseguir esse trabalho no ios 9 também, ligo cell.layoutSubviews()e funcionou.


2

Capturando a captura de tela para sua referênciaPara mim, nenhuma dessas abordagens funcionou, mas descobri que o rótulo tinha um Preferred Widthconjunto explícito no Interface Builder. Removendo isso (desmarcando "Explícito") e usando UITableViewAutomaticDimensionfuncionou como esperado.


2

Tentei todas as soluções nesta página, mas desmarcar usar classes de tamanho e, em seguida, verificar novamente resolveu meu problema.

Edit: Desmarcar as classes de tamanho causa muitos problemas no storyboard, então tentei outra solução. Preenchi minha table view em meu view controller viewDidLoade viewWillAppearmétodos. Isso resolveu meu problema.


1

Tenho o problema de redimensionar o rótulo, então preciso apenas fazer
chatTextLabel.text = chatMessage.message chatTextLabel? .UpdateConstraints () após configurar o texto

// código completo

func setContent() {
    chatTextLabel.text = chatMessage.message
    chatTextLabel?.updateConstraints()

    let labelTextWidth = (chatTextLabel?.intrinsicContentSize().width) ?? 0
    let labelTextHeight = chatTextLabel?.intrinsicContentSize().height

    guard labelTextWidth < originWidth && labelTextHeight <= singleLineRowheight else {
      trailingConstraint?.constant = trailingConstant
      return
    }
    trailingConstraint?.constant = trailingConstant + (originWidth - labelTextWidth)

  }

1

No meu caso, fui atualizando em outro ciclo. Portanto, a altura de tableViewCell foi atualizada após a configuração de labelText. Excluí o bloco assíncrono.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     let cell = tableView.dequeueReusableCell(withIdentifier:Identifier, for: indexPath) 
     // Check your cycle if update cycle is same or not
     // DispatchQueue.main.async {
        cell.label.text = nil
     // }
}

Eu também tenho um bloco assíncrono, mas é necessário? Não há alternativa?
— Nazar Medeiros

1

Apenas certifique-se de não definir o texto do rótulo no método delegado 'willdisplaycell' de visualização de tabela. Defina o texto do rótulo no método delegado 'cellForRowAtindexPath' para cálculo de altura dinâmica.

De nada :)


1

No meu caso, uma exibição de pilha na célula estava causando o problema. Aparentemente, é um bug. Depois de removê-lo, o problema foi resolvido.


1
Tentei todas as outras soluções, apenas a sua solução funcionou, obrigado por compartilhá-la
— tamtoum1987

1

O problema é que as células iniciais são carregadas antes de termos uma altura de linha válida. A solução alternativa é forçar o recarregamento da tabela quando a exibição for exibida.

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}

esta foi a solução muito melhor que me ajudou. Não corrigiu 100% da célula, mas até 80% estavam tomando seu tamanho real. Muito obrigado
— TheTravloper

1

Apenas para iOS 12+, 2019 em diante ...

Um exemplo contínuo da incompetência bizarra ocasional da Apple, onde os problemas duram literalmente anos.

Parece ser o caso de

        cell.layoutIfNeeded()
        return cell

vai consertar isso. (Você está perdendo algum desempenho, é claro.)

Assim é a vida com a Apple.


0

No meu caso, o problema com a altura da célula ocorre depois que a visualização da tabela inicial é carregada e ocorre uma ação do usuário (tocar em um botão em uma célula que tem o efeito de alterar a altura da célula). Não consegui fazer a célula mudar sua altura, a menos que eu:

[self.tableView reloadData];

Eu tentei

[cell layoutIfNeeded];

mas isso não funcionou.


0

No Swift 3. Tive que chamar self.layoutIfNeeded () cada vez que atualizo o texto da célula reutilizável.

import UIKit
import SnapKit

class CommentTableViewCell: UITableViewCell {

    static let reuseIdentifier = "CommentTableViewCell"

    var comment: Comment! {
        didSet {
            textLbl.attributedText = comment.attributedTextToDisplay()
            self.layoutIfNeeded() //This is a fix to make propper automatic dimentions (height).
        }
    }

    internal var textLbl = UILabel()

    override func layoutSubviews() {
        super.layoutSubviews()

        if textLbl.superview == nil {
            textLbl.numberOfLines = 0
            textLbl.lineBreakMode = .byWordWrapping
            self.contentView.addSubview(textLbl)
            textLbl.snp.makeConstraints({ (make) in
                make.left.equalTo(contentView.snp.left).inset(10)
                make.right.equalTo(contentView.snp.right).inset(10)
                make.top.equalTo(contentView.snp.top).inset(10)
                make.bottom.equalTo(contentView.snp.bottom).inset(10)
            })
        }
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let comment = comments[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: CommentTableViewCell.reuseIdentifier, for: indexPath) as! CommentTableViewCell
        cell.selectionStyle = .none
        cell.comment = comment
        return cell
    }

commentsTableView.rowHeight = UITableViewAutomaticDimension
    commentsTableView.estimatedRowHeight = 140

0

Nenhuma das soluções acima funcionou, mas a seguinte combinação de sugestões funcionou.

Tive que adicionar o seguinte em viewDidLoad ().

DispatchQueue.main.async {

        self.tableView.reloadData()

        self.tableView.setNeedsLayout()
        self.tableView.layoutIfNeeded()

        self.tableView.reloadData()

    }

A combinação acima de reloadData, setNeedsLayout e layoutIfNeeded funcionou, mas nenhuma outra. No entanto, pode ser específico para as células do projeto. E sim, tive que invocar reloadData duas vezes para fazê-lo funcionar.

Defina também o seguinte em viewDidLoad

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight

Em tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath)

cell.setNeedsLayout()
cell.layoutIfNeeded() 

Meu semelhante reloadData/beginUpdates/endUpdates/reloadDatatambém está funcionando; reloadData deve ser chamado uma segunda vez. Não precisa embrulhar async.
— raio de

0

Corri para esse problema e corrigi-lo movendo meu código de inicialização view / label DE tableView(willDisplay cell:)PARA tableView(cellForRowAt:).


que NÃO é a maneira recomendada de inicializar uma célula. willDisplayterá um desempenho melhor do que cellForRowAt. Use o último apenas para instanciar a célula certa.
— Martin,

2 anos depois, estou lendo aquele antigo comentário que escrevi. Este foi um conselho ruim. Mesmo que willDisplaytenha melhor desempenho, é recomendável inicializar sua UI de célula cellForRowAtquando o layout for automático. Na verdade, o layout é calculado por UIKit após cellForRowAt et antes de willDisplay. Portanto, se a altura de sua célula depende de seu conteúdo, inicialize o conteúdo do rótulo (ou qualquer outro) em cellForRowAt.
— Martin

-1
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{


//  call the method dynamiclabelHeightForText
}

use o método acima que retorna a altura da linha dinamicamente. E atribua a mesma altura dinâmica à etiqueta que você está usando.

-(int)dynamiclabelHeightForText:(NSString *)text :(int)width :(UIFont *)font
{

    CGSize maximumLabelSize = CGSizeMake(width,2500);

    CGSize expectedLabelSize = [text sizeWithFont:font
                                constrainedToSize:maximumLabelSize
                                    lineBreakMode:NSLineBreakByWordWrapping];


    return expectedLabelSize.height;


}

Este código ajuda a encontrar a altura dinâmica do texto exibido no rótulo.


Na verdade, Ramesh, isso não deve ser necessário, desde que as propriedades tableView.estimatedRowHeight e tableView.rowHeight = UITableViewAutomaticDimension estejam definidas. Além disso, certifique-se de que a célula customizada tenha restrições apropriadas nos vários widgets e no contentView.
— BonanzaDriver de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.