TL; DR: Não gosta de ler? Vá direto para os projetos de amostra no GitHub:
Descrição conceitual
As duas primeiras etapas abaixo são aplicáveis, independentemente de quais versões do iOS você está desenvolvendo.
1. Configurar e adicionar restrições
Na sua UITableViewCell
subclasse, adicione restrições para que as subvisões da célula tenham suas bordas fixadas nas bordas do contentView da célula (o mais importante nas bordas superior E inferior). NOTA: não fixe subvisões à própria célula; apenas para o celular contentView
! Permita que o tamanho do conteúdo intrínseco dessas subvisões conduza a altura da visualização do conteúdo da célula, garantindo que a resistência à compactação e as restrições de restrição de conteúdo na dimensão vertical de cada subvisão não sejam substituídas pelas restrições de prioridade mais alta que você adicionou. ( Hein? Clique aqui. )
Lembre-se de que a ideia é ter as subvisões da célula conectadas verticalmente à visualização de conteúdo da célula, para que possam "exercer pressão" e fazer com que a visualização de conteúdo se expanda para ajustá-las. Usando uma célula de exemplo com algumas subvisões, aqui está uma ilustração visual de como algumas (não todas!) De suas restrições teriam a aparência:
Você pode imaginar que, à medida que mais texto for adicionado ao rótulo do corpo com várias linhas na célula de exemplo acima, ele precisará crescer verticalmente para caber no texto, o que forçará efetivamente a célula a crescer em altura. (Obviamente, você precisa acertar as restrições para que funcione corretamente!)
Ajustar corretamente suas restrições é definitivamente a parte mais difícil e importante de obter alturas dinâmicas de células trabalhando com o Layout Automático. Se você cometer um erro aqui, isso poderá impedir que tudo funcione - por isso não se apresse! Eu recomendo configurar suas restrições no código porque você sabe exatamente quais restrições estão sendo adicionadas e onde é muito mais fácil depurar quando as coisas dão errado. Adicionar restrições no código pode ser tão fácil quanto e significativamente mais poderoso que o Interface Builder usando âncoras de layout ou uma das fantásticas APIs de código aberto disponíveis no GitHub.
- Se você estiver adicionando restrições no código, faça isso uma vez no
updateConstraints
método da sua subclasse UITableViewCell. Observe que updateConstraints
pode ser chamado mais de uma vez; portanto, para evitar adicionar as mesmas restrições mais de uma vez, envolva seu código de adição de restrição updateConstraints
em uma verificação de uma propriedade booleana como didSetupConstraints
(que você definiu como YES depois de executar sua restrição código de adição uma vez). Por outro lado, se você tiver um código que atualize as restrições existentes (como ajustar a constant
propriedade em algumas restrições), coloque-o dentro, updateConstraints
mas fora da verificação, para didSetupConstraints
que ele possa ser executado sempre que o método for chamado.
2. Determinar identificadores exclusivos de reutilização de célula no modo de exibição de tabela
Para cada conjunto exclusivo de restrições na célula, use um identificador de reutilização de célula exclusivo. Em outras palavras, se suas células tiverem mais de um layout exclusivo, cada layout exclusivo receberá seu próprio identificador de reutilização. (Uma boa dica de que você precisa usar um novo identificador de reutilização é quando a variante de célula tem um número diferente de sub-visualizações ou as sub-visualizações são organizadas de maneira distinta.)
Por exemplo, se você estivesse exibindo uma mensagem de email em cada célula, poderá ter quatro layouts exclusivos: mensagens com apenas um assunto, mensagens com um assunto e um corpo, mensagens com um assunto e um anexo de foto e mensagens com um assunto, corpo e anexo de foto. Cada layout possui restrições completamente diferentes necessárias para alcançá-la. Assim, quando a célula é inicializada e as restrições são adicionadas a um desses tipos de células, a célula deve obter um identificador de reutilização exclusivo específico para esse tipo de célula. Isso significa que, quando você desenfileirar uma célula para reutilização, as restrições já foram adicionadas e estão prontas para esse tipo de célula.
Observe que, devido a diferenças no tamanho do conteúdo intrínseco, as células com as mesmas restrições (tipo) ainda podem ter alturas variadas! Não confunda layouts fundamentalmente diferentes (restrições diferentes) com diferentes frames de vista calculados (resolvidos com restrições idênticas) devido a tamanhos diferentes de conteúdo.
- Não adicione células com conjuntos de restrições completamente diferentes ao mesmo pool de reutilização (ou seja, use o mesmo identificador de reutilização) e tente remover as restrições antigas e configurar novas restrições do zero após cada desenfileiramento. O mecanismo interno de Auto Layout não foi projetado para lidar com mudanças em larga escala nas restrições, e você verá grandes problemas de desempenho.
Para iOS 8 - Células de auto-dimensionamento
3. Ative a estimativa de altura da linha
Para habilitar células de exibição de tabela de dimensionamento automático, você deve definir a propriedade rowHeight da exibição de tabela como UITableViewAutomaticDimension. Você também deve atribuir um valor à propriedade ratedRowHeight. Assim que essas duas propriedades são definidas, o sistema usa o Layout automático para calcular a altura real da linha
Apple: Trabalhando com células de exibição de tabela de dimensionamento automático
Com o iOS 8, a Apple internalizou grande parte do trabalho que anteriormente deveria ser implementado por você antes do iOS 8. Para permitir que o mecanismo de célula de auto-dimensionamento funcione, você deve primeiro definir a rowHeight
propriedade na exibição de tabela como constante UITableViewAutomaticDimension
. Em seguida, basta ativar a estimativa da altura da linha configurando a estimatedRowHeight
propriedade da visualização da tabela para um valor diferente de zero, por exemplo:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
O que isso faz é fornecer à visualização da tabela uma estimativa / espaço reservado temporário para as alturas de linha das células que ainda não estão na tela. Então, quando essas células estiverem prestes a rolar na tela, a altura real da linha será calculada. Para determinar a altura real de cada linha, a visualização da tabela pergunta automaticamente a cada célula qual a altura contentView
necessária para se basear na largura fixa conhecida da visualização de conteúdo (que é baseada na largura da visualização da tabela, menos itens adicionais, como um índice de seção ou visualização acessória) e as restrições de layout automáticas adicionadas à visualização e subvisões de conteúdo da célula. Depois que essa altura real da célula é determinada, a altura estimada antiga da linha é atualizada com a nova altura real (e todos os ajustes no contentSize / contentOffset da visualização da tabela são feitos conforme necessário).
De um modo geral, a estimativa que você fornece não precisa ser muito precisa - é usada apenas para dimensionar corretamente o indicador de rolagem na exibição de tabela, e a exibição de tabela faz um bom trabalho ao ajustar o indicador de rolagem para estimativas incorretas, conforme você rolar as células na tela. Você deve definir a estimatedRowHeight
propriedade na visualização da tabela (em viewDidLoad
ou similar) para um valor constante que seja a altura da linha "média". Somente se a altura da sua linha tiver extrema variabilidade (por exemplo, diferir por uma ordem de magnitude) e você notar o indicador de rolagem "pulando" à medida que você rola, você deve se preocupar em implementar tableView:estimatedHeightForRowAtIndexPath:
o cálculo mínimo necessário para retornar uma estimativa mais precisa para cada linha.
Para suporte ao iOS 7 (implementando o dimensionamento automático de células)
3. Faça um passe de layout e obtenha a altura da célula
Primeiro, instancie uma instância fora da tela de uma célula de exibição de tabela, uma instância para cada identificador de reutilização , usada estritamente para cálculos de altura. (Fora da tableView:cellForRowAtIndexPath:
tela, o que significa que a referência da célula é armazenada em uma propriedade / ivar no controlador de exibição e nunca retornada da exibição de tabela para renderizar na tela.) Em seguida, a célula deve ser configurada com o conteúdo exato (por exemplo, texto, imagens etc.) que seria válido se fosse exibido na visualização da tabela.
Então, forçar a célula para de imediato o layout de seus subviews, e depois usar o systemLayoutSizeFittingSize:
método no UITableViewCell
's contentView
para descobrir o que a altura necessária da célula é. Use UILayoutFittingCompressedSize
para obter o menor tamanho necessário para caber em todo o conteúdo da célula. A altura pode então ser retornada do tableView:heightForRowAtIndexPath:
método delegado.
4. Use as alturas estimadas das linhas
Se a visualização da tabela contiver mais de duas dúzias de linhas, você descobrirá que a solução de restrições de Layout Automático pode atolar rapidamente o encadeamento principal ao carregar a visualização da tabela pela primeira vez, como tableView:heightForRowAtIndexPath:
é chamado em todas as linhas após o primeiro carregamento ( para calcular o tamanho do indicador de rolagem).
No iOS 7, você pode (e absolutamente deveria) usar a estimatedRowHeight
propriedade na exibição de tabela. O que isso faz é fornecer à visualização da tabela uma estimativa / espaço reservado temporário para as alturas de linha das células que ainda não estão na tela. Então, quando essas células estiverem prestes a rolar na tela, a altura real da linha será calculada (chamando tableView:heightForRowAtIndexPath:
) e a altura estimada atualizada com a atual.
De um modo geral, a estimativa que você fornece não precisa ser muito precisa - é usada apenas para dimensionar corretamente o indicador de rolagem na exibição de tabela, e a exibição de tabela faz um bom trabalho ao ajustar o indicador de rolagem para estimativas incorretas, conforme você rolar as células na tela. Você deve definir a estimatedRowHeight
propriedade na visualização da tabela (em viewDidLoad
ou similar) para um valor constante que seja a altura da linha "média". Somente se a altura da sua linha tiver extrema variabilidade (por exemplo, diferir por uma ordem de magnitude) e você notar o indicador de rolagem "pulando" à medida que você rola, você deve se preocupar em implementar tableView:estimatedHeightForRowAtIndexPath:
o cálculo mínimo necessário para retornar uma estimativa mais precisa para cada linha.
5. (Se necessário) Adicionar cache de altura da linha
Se você fez tudo o que foi dito acima e ainda está percebendo que o desempenho é inaceitavelmente lento ao resolver as restrições tableView:heightForRowAtIndexPath:
, infelizmente você precisará implementar algum cache para a altura das células. (Essa é a abordagem sugerida pelos engenheiros da Apple.) A idéia geral é permitir que o mecanismo de Autolayout resolva as restrições pela primeira vez, depois armazene em cache a altura calculada para essa célula e use o valor em cache para todas as solicitações futuras da altura dessa célula. O truque, é claro, é garantir que você limpe a altura em cache de uma célula quando acontecer algo que possa causar a alteração da altura da célula - principalmente, quando o conteúdo dessa célula for alterado ou quando ocorrerem outros eventos importantes (como o ajuste do usuário o controle deslizante de tamanho de texto Tipo dinâmico).
Código de exemplo genérico do iOS 7 (com muitos comentários interessantes)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path, depending on the particular layout required (you may have
// just one, or may have many).
NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.
// Note that this method will init and return a new cell if there isn't
// one available in the reuse pool, so either way after this line of
// code you will have a cell with the correct constraints ready to go.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the
// preferredMaxLayoutWidth needs to be set correctly. Do it at this
// point if you are NOT doing it within the UITableViewCell subclass
// -[layoutSubviews] method. For example:
// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path.
NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse
// identifier, creating a cell and storing it in the dictionary if one
// hasn't already been added for the reuse identifier. WARNING: Don't
// call the table view's dequeueReusableCellWithIdentifier: method here
// because this will result in a memory leak as the cell is created but
// never returned from the tableView:cellForRowAtIndexPath: method!
UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[YourTableViewCellClass alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Set the width of the cell to match the width of the table view. This
// is important so that we'll get the correct cell height for different
// table view widths if the cell's height depends on its width (due to
// multi-line UILabels word wrapping, etc). We don't need to do this
// above in -[tableView:cellForRowAtIndexPath] because it happens
// automatically when the cell is used in the table view. Also note,
// the final width of the cell may not be the width of the table view in
// some cases, for example when a section index is displayed along
// the right side of the table view. You must account for the reduced
// cell width.
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Do the layout pass on the cell, which will calculate the frames for
// all the views based on the constraints. (Note that you must set the
// preferredMaxLayoutWidth on multiline UILabels inside the
// -[layoutSubviews] method of the UITableViewCell subclass, or do it
// manually at this point before the below 2 lines!)
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell's contentView
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Add an extra point to the height to account for the cell separator,
// which is added between the bottom of the cell's contentView and the
// bottom of the table view cell.
height += 1.0;
return height;
}
// NOTE: Set the table view's estimatedRowHeight property instead of
// implementing the below method, UNLESS you have extreme variability in
// your row heights and you notice the scroll indicator "jumping"
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do the minimal calculations required to be able to return an
// estimated row height that's within an order of magnitude of the
// actual height. For example:
if ([self isTallCellAtIndexPath:indexPath]) {
return 350.0;
} else {
return 40.0;
}
}
Projetos de amostra
Esses projetos são exemplos completos de visualizações de tabela com alturas de linha variáveis devido às células de visualização de tabela que contêm conteúdo dinâmico nos UILabels.
Xamarin (C # /. NET)
Se você estiver usando o Xamarin, confira este projeto de amostra elaborado por @KentBoogaart .