O UILabel sizeToFit não funciona com a execução automática ios6

152

Como devo configurar programaticamente (e em qual método) um UILabel cuja altura depende do seu texto? Eu tenho tentado configurá-lo usando uma combinação de Storyboard e código, mas sem sucesso. Todo mundo recomenda sizeToFitdurante a configuração lineBreakModee numberOfLines. No entanto, não importa se eu colocar esse código em viewDidLoad:, viewDidAppear:ou viewDidLayoutSubviewseu não posso fazê-lo funcionar. Ou eu faço a caixa muito pequena para texto longo e ela não cresce, ou eu a faço muito grande e não encolhe.

circuitlego
fonte
FWIW o mesmo código: eu não precisava usar label.sizeToFit()no Xcode / viewController, as restrições eram suficientes. não estava criando o rótulo no Playground . Até agora, a única maneira que eu encontrei para trabalhar no campo de jogos é fazerlabel.sizeToFit()
Mel

Respostas:

407

Observe que, na maioria dos casos, a solução de Matt funciona conforme o esperado. Mas se não funcionar, leia mais.

Para fazer com que sua etiqueta redimensione automaticamente a altura, faça o seguinte:

  1. Definir restrições de layout para etiqueta
  2. Defina a restrição de altura com baixa prioridade. Deve ser menor que ContentCompressionResistancePriority
  3. Defina numberOfLines = 0
  4. Defina ContentHuggingPriority mais alto que a prioridade de altura do rótulo
  5. Defina o preferidoMaxLayoutWidth para o rótulo. Esse valor é usado pelo rótulo para calcular sua altura

Por exemplo:

self.descriptionLabel = [[UILabel alloc] init];
self.descriptionLabel.numberOfLines = 0;
self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.descriptionLabel.preferredMaxLayoutWidth = 200;

[self.descriptionLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.descriptionLabel];

NSArray* constrs = [NSLayoutConstraint constraintsWithVisualFormat:@"|-8-[descriptionLabel_]-8-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)];
[self addConstraints:constrs];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[descriptionLabel_]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
[self.descriptionLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[descriptionLabel_(220@300)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];

Usando o Interface Builder

  1. Configure quatro restrições. A restrição de altura é obrigatória. insira a descrição da imagem aqui

  2. Em seguida, vá ao inspetor de atributos do rótulo e defina o número de linhas como 0. insira a descrição da imagem aqui

  3. Vá para o inspetor de tamanho do rótulo e aumente o vertical ContentHuggingPriority e vertical ContentCompressionResistancePriority.
    insira a descrição da imagem aqui

  4. Selecione e edite a restrição de altura.
    insira a descrição da imagem aqui

  5. E diminua a prioridade da restrição de altura.
    insira a descrição da imagem aqui

Aproveitar. :)

Mark Kryzhanouski
fonte
4
SIM! Esta é exatamente a resposta que eu estava procurando. A única coisa que eu precisava fazer era definir o contentHuggingPriority e contentCompressionResistancePriority como obrigatório. Eu poderia fazer tudo isso no IB! Muito obrigado.
circuitlego
43
Você está pensando demais nisso. Defina preferredMaxLayoutWidthou fixe a largura da etiqueta (ou os lados direito e esquerdo). Isso é tudo! Ele irá crescer e encolher automaticamente na vertical para ajustar seu conteúdo.
4183 matt
Eu tive que definir a propriedade preferidaMaxLayoutWidth + fixar a largura do rótulo. Funciona como um encanto :)
MartinMoizard 21/10
preferidoMaxLayoutWidth e / ou fixar os lados esquerdo e direito não é absoluto. As prioridades de abraço e compactação de conteúdo sempre funcionarão desde que sejam mais altas que as demais restrições.
Eric Alford
2
^ E finalmente descobri por que, talvez valha a pena mencionar, quando você tem a célula colada na parte inferior da vista, precisa definir a prioridade da restrição "para baixo" para menos de 1000, eu a coloquei em 750 e tudo está funcionando perfeitamente. Eu acho que encolheu a vista.
Mathijs Segers
65

No iOS 6, usando o layout automático, se os lados (ou largura) e a parte superior de um UILabel forem fixados, ele aumentará e diminuirá automaticamente para ajustar seu conteúdo, sem nenhum código e sem mexer com sua resistência à compressão ou o que seja. É simples demais.

Em casos mais complexos, basta definir os rótulos preferredMaxLayoutWidth.

De qualquer maneira, a coisa certa acontece automaticamente.

mate
fonte
12
Você também precisa definir numberOfLines como 0, caso contrário você não obterá quebra automática.
Devongovett
2
Isso não foi suficiente para mim. Também é necessário o ponto 2 da resposta de @ Mark.
Danyal Aytekin
também é possível fazer a etiqueta crescer automaticamente em várias linhas sem escrever códigos?
Noor
Isso é muito menos complicado que a resposta aceita. Fiz dois exemplos ( aqui e aqui ) ilustrando essa resposta.
Suragch 01/04/19
@ Sururch Usando restrições, isso funciona. Mas não funciona para o mesmo código no Playground . No parque você deve terlabel.sizeToFit()
Mel
42

Embora a pergunta afirme programaticamente, tendo encontrado o mesmo problema e preferindo trabalhar no Interface Builder, pensei que poderia ser útil adicionar às respostas existentes com uma solução do Interface Builder.

A primeira coisa é esquecer sizeToFit. O Layout automático tratará disso em seu nome, com base no tamanho do conteúdo intrínseco.

O problema, portanto, é: como obter um rótulo para ajustar seu conteúdo ao Layout automático? Especificamente - porque a pergunta menciona isso - altura. Observe que os mesmos princípios se aplicam à largura.

Então, vamos começar com um exemplo UILabel que tem uma altura definida para 41px de altura:

insira a descrição da imagem aqui

Como você pode ver na tela acima, "This is my text"tem preenchimento acima e abaixo. Isso está entre a altura do UILabel e seu conteúdo, o texto.

Se executarmos o aplicativo no simulador, com certeza, vemos a mesma coisa:

insira a descrição da imagem aqui

Agora, vamos selecionar o UILabel no Interface Builder e dar uma olhada nas configurações padrão no inspetor Size:

insira a descrição da imagem aqui

Observe a restrição destacada acima. Essa é a prioridade de abraçar o conteúdo . Como Erica Sadun o descreve no excelente iOS Auto Layout Demystified , este é:

da maneira que uma visualização prefere evitar preenchimento extra em torno do conteúdo principal

Para nós, com o UILabel, o conteúdo principal é o texto.

Aqui chegamos ao cerne desse cenário básico. Demos ao nosso rótulo de texto duas restrições. Eles conflitam. Diz-se "a altura deve ser igual a 41 pixels de altura" . O outro diz: "abrace a exibição do conteúdo para que não tenhamos nenhum preenchimento extra" . No nosso caso, abrace a visualização do texto para que não tenhamos nenhum preenchimento extra.

Agora, com o Auto Layout, com duas instruções diferentes que dizem fazer coisas diferentes, o tempo de execução precisa escolher uma ou outra. Não pode fazer as duas coisas. O UILabel não pode ter 41 pixels de altura e não possui preenchimento.

A maneira como isso é resolvido é especificando a prioridade. Uma instrução precisa ter uma prioridade mais alta que a outra. Se as duas instruções disserem coisas diferentes e tiverem a mesma prioridade, ocorrerá uma exceção.

Então, vamos tentar. Minha restrição de altura tem uma prioridade de 1000 , o que é necessário . A altura de abraço do conteúdo é 250 , o que é fraco . O que acontece se reduzirmos a prioridade de restrição de altura para 249 ?

insira a descrição da imagem aqui

Agora podemos ver a mágica começar a acontecer. Vamos tentar no sim:

insira a descrição da imagem aqui

Impressionante! Abraçando o conteúdo alcançado. Somente porque a prioridade de altura 249 é menor que o conteúdo que abraça a prioridade 250 . Basicamente, estou dizendo "a altura que eu especifico aqui é menos importante do que eu especifiquei para o abraço do conteúdo" . Então, o conteúdo que abraça vence.

Resumindo, conseguir que o rótulo caiba no texto pode ser tão simples quanto especificar a restrição de altura ou largura e definir corretamente essa prioridade em associação com o conteúdo desse eixo que abraça a restrição de prioridade.

Deixará fazer o equivalente à largura como um exercício para o leitor!

Max MacLeod
fonte
3
Muito obrigado pela sua ótima explicação! Finalmente, eu entendo o conteúdo que abraça a prioridade
Kernix 6/03/14
Você poderia explicar também o que é a prioridade de resistência à compressão de conteúdo? Obrigado!
Kernix 6/03
2
Excelente resposta Max! Existe alguma maneira de limitar o número de linhas que funcionam dessa maneira?
rafaeljuzo
7

Notado no IOS7 sizeToFit também não estava funcionando - talvez a solução também possa ajudá-lo

[textView sizeToFit];
[textView layoutIfNeeded];
Nick Wood
fonte
1
@Rambatino Isso funciona para mim. Adicione layoutIfNeededquando precisar setNeedsUpdateConstraints. Mas a situação pode ser um pouco diferente.
Gon
Isso funcionou melhor para mim quando fiz uma popover. No entanto, eu precisava fazer o layoutIfNeeded primeiro
Jason
4

Outra opção para garantir que o preferencialMaxLayoutWidth do rótulo seja mantido sincronizado com a largura do rótulo:

#import "MyLabel.h"

@implementation MyLabel

-(void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];

    // This appears to be needed for iOS 6 which doesn't seem to keep
    // label preferredMaxLayoutWidth in sync with its width, which 
    // means the label won't grow vertically to encompass its text if 
    // the label's width constraint changes.
    self.preferredMaxLayoutWidth = self.bounds.size.width;
}

@end
Monte Hurd
fonte
4

Sinto que devo contribuir, pois demorei um pouco para encontrar a solução certa:

  • O objetivo é permitir que o Auto Layout faça seu trabalho sem precisar chamar sizeToFit (), faremos isso especificando as restrições corretas:
  • Especifique as restrições de espaço superior, inferior e à esquerda / à direita no seu UILabel
  • Defina a propriedade número de linhas como 0
  • Incremente o conteúdo que prioriza 1000
  • Reduza a prioridade da resistência à compressão de conteúdo para 500
  • Na sua restrição de contêiner inferior, reduza a prioridade para 500

Basicamente, o que acontece é que você diz ao seu UILabel que, embora ele tenha uma restrição de altura fixa, ele pode quebrar a restrição e se tornar menor para abraçar o conteúdo (se você tiver uma única linha, por exemplo), mas não pode quebre a restrição para aumentá-la.

ArturT
fonte
3

No meu caso, eu estava criando uma subclasse UIView que continha um UILabel (de tamanho desconhecido). No iOS7, o código era direto: defina as restrições, não se preocupe com o abraço do conteúdo ou a resistência à compactação e tudo funcionou conforme o esperado.

Mas no iOS6, o UILabel era sempre cortado em uma única linha. Nenhuma das respostas acima funcionou para mim. As configurações de resistência a compressão e aperto de conteúdo foram ignoradas. A única solução que impediu o recorte foi incluir um MaxMayLayoutWidth preferido no rótulo. Mas eu não sabia como definir a largura preferida, pois o tamanho da visualização pai era desconhecido (na verdade, seria definido pelo conteúdo).

Finalmente encontrei a solução aqui . Como estava trabalhando em uma exibição personalizada, era possível adicionar o método a seguir para definir a largura de layout preferida após o cálculo das restrições uma vez e depois recalculá-las:

- (void)layoutSubviews
{
    // Autolayout hack required for iOS6
    [super layoutSubviews];
    self.bodyLabel.preferredMaxLayoutWidth = self.bodyLabel.frame.size.width;
    [super layoutSubviews];
}
sumizome
fonte
Este foi o cenário exato que eu tive ... às vezes é bom saber que você não está sozinho.
DaveMac
Adam, estou lutando com o que você disse que funcionou perfeitamente no ios7. Eu tenho uma célula personalizada com uma visão geral e um uilabel. Posso fazer com que o rótulo caiba no conteúdo, mas a visualização não, independentemente das restrições que eu coloquei nele. Como você fez isto funcionar? Eu juro que tentei de tudo, mas não demorou a altura do rótulo
denikov
3

Adicionei UILabelprogramaticamente e, no meu caso, foi o suficiente:

label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0
mixel
fonte
2

Eu resolvi com o xCode6 colocando "Largura preferida" em Automático e fixando a parte superior da etiqueta, inicial e final insira a descrição da imagem aqui

Kazzar
fonte
0
UIFont *customFont = myLabel.font;
CGSize size = [trackerStr sizeWithFont:customFont
                             constrainedToSize:myLabel.frame.size // the size here should be the maximum size you want give to the label
                                 lineBreakMode:UILineBreakModeWordWrap];
float numberOfLines = size.height / customFont.lineHeight;
myLabel.numberOfLines = numberOfLines;
myLabel.frame = CGRectMake(258, 18, 224, (numberOfLines * customFont.lineHeight));
Swati
fonte
0

Corri para esse problema também com uma subclasse UIView que contém um UILabel como um se seus elementos internos. Mesmo com o layout automático e tentando todas as soluções recomendadas, o rótulo simplesmente não aumentava sua altura ao texto. No meu caso, eu só quero que o rótulo seja uma linha.

De qualquer forma, o que funcionou para mim foi adicionar uma restrição de altura necessária para o UILabel e configurá-la manualmente para a altura correta quando o intrinsicContentSize for chamado. Se você não tiver o UILabel contido em outro UIView, tente subclassificar o UILabel e fornecer uma implementação semelhante definindo primeiro a restrição de altura e depois retornando
[super instrinsicContentSize]; em vez de [self.containerview intrinsiceContentSize]; como eu faço abaixo, que é específico para o sublass UIView.

- (CGSize)intrinsicContentSize
{
    CGRect expectedTextBounds = [self.titleLabel textRectForBounds:self.titleLabel.bounds limitedToNumberOfLines:1];
    self.titleLabelHeightConstraint.constant = expectedTextBounds.size.height;
    return [self.containerView intrinsicContentSize];

}

Agora funciona perfeitamente no iOS 7 e no iOS 8.

n8tr
fonte
Vale a pena notar que, se você configurou sua exibição corretamente (ou seja, suas restrições estão corretas e configurou tudo durante os estágios corretos no ciclo de vida da exibição), nunca será necessário substituir intrinsicContentSize. O tempo de execução deve poder calcular isso com base nas restrições de configuração correta.
John Rogers
Na teoria, isso deve estar correto, mas não temos acesso a todas as APIs e implementações privadas da Apple e elas não são infalíveis e possuem bugs em seu código.
n8tr
... mas [super intrinsicContentSize] deve cuidar disso, é o que estou dizendo. Se você implementou seu layout automático corretamente.
John Rogers
0

Uma solução que funcionou para mim; Se o seu UILabel tiver uma largura fixa, altere a restrição deconstant = para constant <=no seu arquivo de interface

insira a descrição da imagem aqui

Alex Brown
fonte
-1

No meu caso, ao usar os rótulos em um UITableViewCell, o rótulo em seria redimensionado, mas a altura ultrapassaria a altura da célula da tabela. Isto é o que funcionou para mim. Fiz de acordo com Max MacLeod e, em seguida, verifiquei se a altura da célula estava definida como UITableViewAutomaticDimension.

Você pode adicionar isso em seu init ou awakeFromNib,

self.tableView.rowHeight = UITableViewAutomaticDimension.  

No storyboard, selecione a célula, abra o Inspetor de tamanho e verifique se a altura da linha está definida como "Padrão" marcando a caixa de seleção "Personalizado".

Há um problema no 8.0 que também exige que ele seja definido no código.

Maria
fonte