boundingRectWithSize for NSAttributedString retornando tamanho errado

151

Estou tentando obter o ret para uma string atribuída, mas a chamada boundingRectWithSize não está respeitando o tamanho que eu passo e está retornando um ret com uma altura de linha única em oposição a uma altura grande (é uma string longa). Eu experimentei passando um valor muito grande para a altura e também 0 como no código abaixo, mas o rect retornado é sempre o mesmo.

CGRect paragraphRect = [attributedText boundingRectWithSize:CGSizeMake(300,0.0)
  options:NSStringDrawingUsesDeviceMetrics
  context:nil];

Isso está quebrado ou preciso fazer outra coisa para que ele retorne um retângulo para o texto agrupado?

RunLoop
fonte
5
Você tem um estilo de parágrafo com truncamento / recorte lineBreakMode?
Danyowdee 31/12/12
1
se você estiver lendo isso porque o UILabel mede / quebra de largura incorreta, consulte stackoverflow.com/questions/46200027/… . especificamente, definindo NSAllowsDefaultLineBreakStrategy como false no lançamento do aplicativo.
eric

Respostas:

312

Parece que você não estava fornecendo as opções corretas. Para embalar etiquetas, forneça pelo menos:

CGRect paragraphRect =
  [attributedText boundingRectWithSize:CGSizeMake(300.f, CGFLOAT_MAX)
  options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
  context:nil];

Nota: se a largura do texto original for inferior a 300.f, não haverá quebra de linha; verifique se o tamanho do limite está correto; caso contrário, você ainda obterá resultados incorretos.

Ed McManus
fonte
25
Todo mundo precisa olhar para esta resposta, porque funciona. Talvez seja necessária documentação mais clara para esse método e cadeias atribuídas em geral; não é óbvio para onde você precisa procurar.
Macserv # 28/13
31
Atenção! Nem sempre funciona! Às vezes, a largura retornada é maior que a largura do parâmetro size. A documentação do método Even Apples declara: "As restrições que você especifica no parâmetro size são um guia para o representante de como dimensionar a string. No entanto, o retângulo delimitador real retornado por esse método pode ser maior que as restrições se for necessário espaço adicional para renderize a string inteira ".
Klaas
75
A API para NSAttributedString e boundingRectWithSize é absolutamente chocante.
malhal
27
Outra dica é que a altura (e presumivelmente a largura) retornada no paragraphRect é quase sempre um valor fracionário. Portanto, pode aparecer dizendo 141,3 como a altura. Você precisa usar o resultado de ceilf(paragraphRect.size.height)para que ele seja arredondado. Eu esqueço isso o tempo todo e me pergunto por que minhas etiquetas ainda estão cortadas.
jamone
39
Eu sempre boundingRectWithSize:envolvo meus cálculos em CGRectIntegral (), que CGRectIntegral rounds the rectangle’s origin downward and its size upward to the nearest whole integersneste caso arredondará a altura e a largura para garantir que nenhum recorte ocorra se a altura ou largura for um valor fracionário.
runmad
47

Por alguma razão, boundingRectWithSize sempre retorna tamanho errado. Eu descobri uma solução. Existe um método para UItextView -sizeThatFits que retorna o tamanho adequado para o conjunto de texto. Portanto, em vez de usar boundingRectWithSize, crie um UITextView, com um quadro aleatório e chame seu sizeThatFits com a respectiva largura e altura CGFLOAT_MAX. Retorna o tamanho que terá a altura adequada.

   UITextView *view=[[UITextView alloc] initWithFrame:CGRectMake(0, 0, width, 10)];   
   view.text=text;
   CGSize size=[view sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)];
   height=size.height; 

Se você estiver calculando o tamanho em um loop while, não se esqueça de adicioná-lo em um pool de liberação automática, pois haverá um número n de UITextView criado, a memória do tempo de execução do aplicativo aumentará se não usarmos o recurso de liberação automática.

shoan
fonte
1
Isso funciona. As outras maneiras que eu tentei, nunca consegui trabalhar. Acho que meu problema decorre da complexidade da sequência atribuída que eu estava atribuindo. Era proveniente de um arquivo RTF e usava uma variedade de fontes, espaçamento entre linhas e até sombras, e apesar de usar UsesLineFragmentOrigin e UsesFontLeading, nunca consegui um resultado que fosse suficientemente alto e o problema era pior quanto mais tempo a string atribuída. Meu palpite é que, para cadeias relativamente simples, o método boundingRectWithSize pode funcionar. Eles realmente precisam de um método melhor de trabalhar com isso, imo.
John Bushnell
você não acha que é um exagero criar UITextViewquantas vezes o heightForRowAtIndexPathmétodo for chamado? leva tempo
János
Eu concordo com você @ János, mas esta foi a única solução que funcionou para mim.
Shoan
btw eu pedi a solução adequada: stackoverflow.com/questions/32495744/…
János
esta é a única solução que funciona. Esse problema tem 9 anos e a Apple aparentemente não quer resolver isso, ou seus engenheiros estão fracos para descobrir uma solução para isso. Estamos falando aqui do iOS 11, ainda carregando o mesmo bug.
Duck
31

Ed McManus certamente forneceu uma chave para fazer isso funcionar. Encontrei um caso que não funciona

UIFont *font = ...
UIColor *color = ...
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                     font, NSFontAttributeName,
                                     color, NSForegroundColorAttributeName,
                                     nil];

NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString: someString attributes:attributesDictionary];

[string appendAttributedString: [[NSAttributedString alloc] initWithString: anotherString];

CGRect rect = [string boundingRectWithSize:constraint options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];

rect não terá a altura correta. Observe que anotherString (que é anexado à string ) foi inicializado sem um dicionário de atributo. Este é um inicializador legítimo para anotherString, mas boundingRectWithSize: não fornece um tamanho exato nesse caso.

Paul Heller
fonte
33
Obrigado! Acontece que TODA parte de um NSAttributedString deve ter um dicionário definido com pelo menos NSFontAttributeName e NSForegroundColorAttributeName, se você desejar que boundingRectWithSize realmente funcione! Não vejo isso documentado em lugar algum.
Ben Wheeler
3
Observe que também parece que as NSAttributedStrings diferentes combinadas para criar uma única devem usar o dicionário SAME (e, portanto, a mesma fonte) para que boundingRectWithSize funcione corretamente!
Ben Wheeler
1
Essa solução é muito semelhante à que eu uso na minha categoria UILabel para "reduzir para caber". A chave para eu fazer isso funcionar foi criar o retângulo delimitador com a altura FLT_MAX. Sem isso, eu pareceria obter um retângulo para apenas uma linha.
precisa saber é o seguinte
Marcam com +1 o tempo todo. Isso realmente me pegou, então obrigado por resolver isso, pessoal.
Wex
Eu tive esse problema. Usei espaços e novas linhas NSMutableAttributedStringsem atributos e estava obtendo tamanho errado.
vbezhenar
28

Minha decisão final após uma longa investigação: a
- boundingRectWithSizefunção retorna o tamanho correto apenas para a sequência ininterrupta de caracteres! Caso a string contenha espaços ou algo mais (chamado pela Apple de "Alguns dos glifos") - é impossível obter o tamanho real de rect necessário para exibir o texto!
Substituí os espaços nas minhas strings por letras e obtive imediatamente o resultado correto.

A Apple diz aqui: https://developer.apple.com/documentation/foundation/nsstring/1524729-boundingrectwithsize

"Este método retorna os limites reais dos glifos na sequência. É permitido que alguns dos glifos (espaços, por exemplo) se sobreponham às restrições de layout especificadas pelo tamanho passado, portanto, em alguns casos, o valor da largura do componente de tamanho de o retorno CGRectpode exceder o valor da largura do parâmetro size ".

Portanto, é necessário encontrar outra maneira de calcular o retângulo real ...


Após um longo processo de investigação, a solução finalmente foi encontrada !!! Não tenho certeza de que funcionará bem em todos os casos relacionados UITextView, mas foi detectado o principal e o importante!

boundingRectWithSizeA função, assim como CTFramesetterSuggestFrameSizeWithConstraints(e muitos outros métodos), calculará o tamanho e a parte do texto corretos quando o retângulo correto for usado. Por exemplo - UITextViewhas textView.bounds.size.width- e esse valor não é um retângulo real usado pelo sistema ao desenhar o texto UITextView.

Achei um parâmetro muito interessante e realizei um cálculo simples no código:

CGFloat padding = textView.textContainer.lineFragmentPadding;  
CGFloat  actualPageWidth = textView.bounds.size.width - padding * 2;

E obras mágicas - todos os meus textos calculados estão corretos agora! Aproveitar!

Юрий Сталоверов
fonte
1
sim, notei que também, ele não mantém as restrições de largura, sempre sai maior. Isso não é legal, eles depreciam o método de trabalho e agora temos que lidar com essa porcaria.
Boris Gafurov 23/09
1
Você senhor, é meu novo herói! Isso estava me deixando louco. Estou definindo as inserções textContainers, então tive que usar textView.textContainer.size.width em vez de textView.bounds.size.witdh.
precisa saber é o seguinte
Eu não poderia votar isso o suficiente. Tão estranho que os espaços não são considerados nos cálculos delimitadores.
Perseguição Holanda
1
Substituindo os espaços com algum personagem fictício como "3" Retorna a altura exata
Abuzar Amin
1
isso salvou minha carreira e meu relacionamento com a equipe de controle de qualidade. Te devo um homem cerveja!
Lucaslt89 #
14

Swift four version

let string = "A great test string."
let font = UIFont.systemFont(ofSize: 14)
let attributes: [NSAttributedStringKey: Any] = [.font: font]
let attributedString = NSAttributedString(string: string, attributes: attributes)
let largestSize = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)

//Option one (best option)
let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
let textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(), nil, largestSize, nil)

//Option two
let textSize = (string as NSString).boundingRect(with: largestSize, options: [.usesLineFragmentOrigin , .usesFontLeading], attributes: attributes, context: nil).size

//Option three
let textSize = attributedString.boundingRect(with: largestSize, options: [.usesLineFragmentOrigin , .usesFontLeading], context: nil).size

A medição do texto com o CTFramesetter funciona melhor, pois fornece tamanhos inteiros e manipula bem os emoji e outros caracteres unicode.

David Rees
fonte
10

Não tive sorte com nenhuma dessas sugestões. Minha string continha pontos de marcador unicode e eu suspeito que eles estavam causando sofrimento no cálculo. Percebi que o UITextView estava lidando bem com o desenho, então procurei aproveitar esse cálculo. Fiz o seguinte, que provavelmente não é tão ideal quanto os métodos de desenho NSString, mas pelo menos é preciso. Também é um pouco mais ideal do que inicializar um UITextView apenas para chamar -sizeThatFits:.

NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(width, CGFLOAT_MAX)];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[layoutManager addTextContainer:textContainer];

NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:formattedString];
[textStorage addLayoutManager:layoutManager];

const CGFloat formattedStringHeight = ceilf([layoutManager usedRectForTextContainer:textContainer].size.height);
Ricky
fonte
Pode não ter sido os pontos unicode, pode haver espaços no final da sua string. Veja a resposta acima: stackoverflow.com/a/25941139/337934 .
SamB 27/03
9

Caso você queira obter uma caixa delimitadora truncando a cauda, esta pergunta pode ajudá-lo.

CGFloat maxTitleWidth = 200;

NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = NSLineBreakByTruncatingTail;

NSDictionary *attributes = @{NSFontAttributeName : self.textLabel.font,
                             NSParagraphStyleAttributeName: paragraph};

CGRect box = [self.textLabel.text
              boundingRectWithSize:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)
              options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
              attributes:attributes context:nil];
Roman B.
fonte
9

Acontece que TODAS as partes de um NSAttributedString devem ter um dicionário definido com pelo menos NSFontAttributeName e NSForegroundColorAttributeName, se você desejar que boundingRectWithSize realmente funcione!

Não vejo isso documentado em lugar algum.

Ben Wheeler
fonte
Obrigado, esta foi a solução para mim, exceto que eu achei que apenas NSFontAttributeName era necessário, não NSForegroundColorAttributeName
Marmoy
7

@warrenm Lamento dizer que o método do framesetter não funcionou para mim.

Esta função pode nos ajudar a determinar o tamanho do quadro necessário para um intervalo de cadeias de caracteres de um NSAttributedString no iphone / Ipad SDK para uma determinada largura:

Pode ser usado para uma altura dinâmica de células UITableView

- (CGSize)frameSizeForAttributedString:(NSAttributedString *)attributedString
{
    CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
    CGFloat width = YOUR_FIXED_WIDTH;

    CFIndex offset = 0, length;
    CGFloat y = 0;
    do {
        length = CTTypesetterSuggestLineBreak(typesetter, offset, width);
        CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(offset, length));

        CGFloat ascent, descent, leading;
        CTLineGetTypographicBounds(line, &ascent, &descent, &leading);

        CFRelease(line);

        offset += length;
        y += ascent + descent + leading;
    } while (offset < [attributedString length]);

    CFRelease(typesetter);

    return CGSizeMake(width, ceil(y));
}

Agradecimentos a HADDAD ISSA >>> http://haddadissa.blogspot.in/2010/09/compute-needed-heigh-for-fixed-width-of.html

Karan Alangat
fonte
Não funciona para mim em nenhum dispositivo (iPhone 4 e 5). Tanto no dispositivo com iOS7.1.2 e 4s Simulator com iOS8.3 :(
sanjana
Isso precisa de alguma importação?
Jose920405
@KaranAlangat sim#import <CoreText/CoreText.h>
jose920405
7

Descobri que a solução preferida não lida com quebras de linha.

Eu descobri que essa abordagem funciona em todos os casos:

UILabel* dummyLabel = [UILabel new];
[dummyLabel setFrame:CGRectMake(0, 0, desiredWidth, CGFLOAT_MAX)];
dummyLabel.numberOfLines = 0;
[dummyLabel setLineBreakMode:NSLineBreakByWordWrapping];
dummyLabel.attributedText = myString;
[dummyLabel sizeToFit];
CGSize requiredSize = dummyLabel.frame.size;
dmc
fonte
No meu caso eu deveria fazer o cálculo na discussão de fundo, e uso de elementos de interface do usuário não é permitido
Lubbo
4

Eu tive o mesmo problema ao não obter um tamanho exato usando essas técnicas e mudei minha abordagem para fazê-la funcionar.

Eu tenho uma longa seqüência de caracteres atribuída que eu tenho tentado ajustar em uma exibição de rolagem para que ela seja exibida corretamente sem ser truncada. O que fiz para fazer o texto funcionar de maneira confiável foi não definir a altura como uma restrição e, em vez disso, permitir que o tamanho intrínseco assumisse o controle. Agora o texto é exibido corretamente sem ser truncado e não preciso calcular a altura.

Suponho que, se eu precisasse obter a altura de forma confiável, criaria uma exibição oculta e essas restrições e obteria a altura do quadro depois que as restrições fossem aplicadas.

Brennan
fonte
2
Você pode adicionar um exemplo de código para demonstrar isso? THX.
precisa saber é o seguinte
3

Estou um pouco atrasado para o jogo - mas tenho tentado descobrir uma maneira que funcione para encontrar a caixa delimitadora que caiba em torno de uma sequência atribuída para fazer um foco soar como a edição de um arquivo no Finder. tudo o que eu tentei falhou quando há espaços no final da string ou vários espaços dentro da string. boundingRectWithSizefalha miseravelmente por isso e também CTFramesetterCreateWithAttributedString.

Usar um NSLayoutManagercódigo a seguir parece fazer o truque em todos os casos que encontrei até agora e retorna um ret que limita perfeitamente a string. Bônus: se você selecionar o texto, as bordas da seleção vão até os limites do retângulo retornado. O código abaixo usa o layoutManager de a NSTextView.

NSLayoutManager* layout = [self layoutManager];
NSTextContainer* container = [self textContainer];

CGRect focusRingFrame = [layout boundingRectForGlyphRange:NSMakeRange(0, [[self textStorage] length]) inTextContainer:container];
Chris Walken
fonte
2
textView.textContainerInset = UIEdgeInsetsZero;
NSString *string = @"Some string";
NSDictionary *attributes = @{NSFontAttributeName:[UIFont systemFontOfSize:12.0f], NSForegroundColorAttributeName:[UIColor blackColor]};
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
[textView setAttributedText:attributedString];
CGRect textViewFrame = [textView.attributedText boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.view.frame)-8.0f, 9999.0f) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];
NSLog(@"%f", ceilf(textViewFrame.size.height));

Funciona perfeitamente em todas as fontes!

Dmitry Coolerov
fonte
1

Eu tive o mesmo problema, mas reconheci que a restrição de altura foi definida corretamente. Então eu fiz o seguinte:

-(CGSize)MaxHeighForTextInRow:(NSString *)RowText width:(float)UITextviewWidth {

    CGSize constrainedSize = CGSizeMake(UITextviewWidth, CGFLOAT_MAX);

    NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                          [UIFont fontWithName:@"HelveticaNeue" size:11.0], NSFontAttributeName,
                                          nil];

    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:RowText attributes:attributesDictionary];

    CGRect requiredHeight = [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil];

    if (requiredHeight.size.width > UITextviewWidth) {
        requiredHeight = CGRectMake(0, 0, UITextviewWidth, requiredHeight.size.height);
    }

    return requiredHeight.size;
}
Marcel
fonte
1
    NSDictionary *stringAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                      [UIFont systemFontOfSize:18], NSFontAttributeName,
                                      [UIColor blackColor], NSForegroundColorAttributeName,
                                      nil];

    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:myLabel.text attributes:stringAttributes];
    myLabel.attributedText = attributedString; //this is the key!

    CGSize maximumLabelSize = CGSizeMake (screenRect.size.width - 40, CGFLOAT_MAX);

    CGRect newRect = [myLabel.text boundingRectWithSize:maximumLabelSize
                                                       options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                                    attributes:stringAttributes context:nil];

    self.myLabelHeightConstraint.constant = ceilf(newRect.size.height);

Eu tentei de tudo nesta página e ainda tinha um caso para um UILabel que não estava formatando corretamente. A definição do atributoText na etiqueta finalmente resolveu o problema.

Lil
fonte
1

Uma coisa que eu estava notando é que o retângulo que retornaria (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)contextteria uma largura maior do que a que passei. Quando isso acontecesse, minha corda seria truncada. Eu resolvi assim:

NSString *aLongString = ...
NSInteger width = //some width;            
UIFont *font = //your font;
CGRect rect = [aLongString boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
                                        options:(NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin)
                                     attributes:@{ NSFontAttributeName : font,
                                                   NSForegroundColorAttributeName : [UIColor whiteColor]}
                                        context:nil];

if(rect.size.width > width)
{
    return rect.size.height + font.lineHeight;
}
return rect.size.height;

Para um pouco mais de contexto; Eu tinha texto com várias linhas e estava tentando encontrar a altura certa para exibi-lo. BoundRectWithSize às vezes retornava uma largura maior do que o que eu especificaria, portanto, quando eu usava meu passado em largura e a altura calculada para exibir meu texto, ele truncaria. Nos testes, quando boundingRectWithSize usava a largura errada, a quantidade que tornaria a altura menor era de 1 linha. Portanto, eu verificaria se a largura era maior e, em caso afirmativo, adicione o lineHeight da fonte para fornecer espaço suficiente para evitar o truncamento.

odyth
fonte
Como a altura da linha está afetando com Width?
Roi Mulia 28/05
A altura da linha @RoiMulia não afeta a largura. Atualizei minha resposta para fornecer mais contexto sobre como isso corrigiu meu erro.
odyth
0
    NSAttributedString *attributedText =[[[NSAttributedString alloc]
                                          initWithString:joyMeComment.content
                                          attributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:TextFont]}] autorelease];

    CGRect paragraphRect =
    [attributedText boundingRectWithSize:CGSizeMake(kWith, CGFLOAT_MAX)
                                 options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                 context:nil];
    contentSize = paragraphRect.size;

    contentSize.size.height+=10;
    label.frame=contentSize;

se o quadro da etiqueta não adicionar 10, esse método nunca funcionará! espero que isso possa ajudá-lo! Boa sorte.

MICHEAL LV
fonte
0
Add Following methods in ur code for getting correct size of attribute string 
1.
    - (CGFloat)findHeightForText:(NSAttributedString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font
 {
    UITextView *textView = [[UITextView alloc] init];
    [textView setAttributedText:text];
    [textView setFont:font];
    CGSize size = [textView sizeThatFits:CGSizeMake(widthValue, FLT_MAX)];
    return size.height;

}

2. Call on heightForRowAtIndexPath method
     int h = [self findHeightForText:attrString havingWidth:yourScreenWidth andFont:urFont];
Anny
fonte
No meu caso eu deveria fazer o cálculo na discussão de fundo, e uso de elementos de interface do usuário não é permitido
Lubbo
0

Gostaria de acrescentar meus pensamentos, já que tive exatamente o mesmo problema.

Eu estava usando, UITextViewpois tinha um alinhamento de texto melhor (justifique, que no momento não estava disponível UILabel), mas para "simular" não interativo-não-rolável UILabel, eu desativaria completamente a interação de rolagem, salto e interação do usuário .

Obviamente, o problema era que o texto era dinâmico e, embora a largura fosse fixa, a altura deveria ser recalculada toda vez que eu definir um novo valor de texto.

boundingRectWithSizenão funcionou bem para mim, pelo que pude ver, UITextViewestava adicionando uma margem na parte superior que boundingRectWithSizenão entrava em contagem, portanto, a altura recuperada boundingRectWithSizeera menor do que deveria.

Como o texto não deveria ser atualizado rapidamente, ele é usado apenas para algumas informações que podem ser atualizadas a cada 2-3 segundos, decidi a seguinte abordagem:

/* This f is nested in a custom UIView-inherited class that is built using xib file */
-(void) setTextAndAutoSize:(NSString*)text inTextView:(UITextView*)tv
{
    CGFloat msgWidth = tv.frame.size.width; // get target's width

    // Make "test" UITextView to calculate correct size
    UITextView *temp = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, msgWidth, 300)]; // we set some height, really doesn't matter, just put some value like this one.
    // Set all font and text related parameters to be exact as the ones in targeted text view
    [temp setFont:tv.font];
    [temp setTextAlignment:tv.textAlignment];
    [temp setTextColor:tv.textColor];
    [temp setText:text];

    // Ask for size that fits :P
    CGSize tv_size = [temp sizeThatFits:CGSizeMake(msgWidth, 300)];

    // kill this "test" UITextView, it's purpose is over
    [temp release];
    temp = nil;

    // apply calculated size. if calcualted width differs, I choose to ignore it anyway and use only height because I want to have width absolutely fixed to designed value
    tv.frame = CGRectMake(tv.frame.origin.x, tv.frame.origin.y, msgWidth, tv_size.height );
}

* O código acima não é copiado diretamente da minha fonte, eu tive que ajustá-lo / limpá-lo de várias outras coisas não necessárias para este artigo. Não aceite o código copiar-colar-e-ele-funcionará.

A desvantagem óbvia é que ele tem alocação e liberação para cada chamada.

Mas, a vantagem é que você evite dependendo da compatibilidade entre a forma como boundingRectWithSize desenha texto e calcula o seu tamanho e implementação de desenho de texto em UITextView(ou UILabelo que também é possível utilizar apenas substituir UITextViewcom UILabel). Quaisquer "bugs" que a Apple possa ter são assim evitados.

PS: Parece que você não precisa desse "temp" UITextViewe pode apenas perguntar sizeThatFitsdiretamente do alvo, mas isso não funcionou para mim. Embora a lógica diga que deve funcionar e que a alocação / liberação temporária UITextViewnão é necessária, ela não funcionou. Mas essa solução funcionou perfeitamente para qualquer texto que eu inserisse.

Sinisa
fonte
No meu caso eu deveria fazer o cálculo na discussão de fundo, e uso de elementos de interface do usuário não é permitido
Lubbo
0

Ok, então eu passei muito tempo depurando isso. Descobri que a altura máxima do texto, conforme definida pela boundingRectWithSizepermissão para exibir texto por minha, UITextViewera menor que o tamanho do quadro.

No meu caso, o quadro é no máximo 140pt, mas o UITextView tolera no máximo 131pt.

Eu tive que descobrir isso manualmente e codificar a altura máxima "real".

Aqui está a minha solução:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    NSString *proposedText = [textView.text stringByReplacingCharactersInRange:range withString:text];
    NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:proposedText];
    CGRect boundingRect;
    CGFloat maxFontSize = 100;
    CGFloat minFontSize = 30;
    CGFloat fontSize = maxFontSize + 1;
    BOOL fit;
    NSLog(@"Trying text: \"%@\"", proposedText);
    do {
        fontSize -= 1;
        //XXX Seems like trailing whitespaces count for 0. find a workaround
        [attributedText addAttribute:NSFontAttributeName value:[textView.font fontWithSize:fontSize] range:NSMakeRange(0, attributedText.length)];
        CGFloat padding = textView.textContainer.lineFragmentPadding;
        CGSize boundingSize = CGSizeMake(textView.frame.size.width - padding * 2, CGFLOAT_MAX);
        boundingRect = [attributedText boundingRectWithSize:boundingSize options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil];
        NSLog(@"bounding rect for font %f is %@; (max is %f %f). Padding: %f", fontSize, NSStringFromCGRect(boundingRect), textView.frame.size.width, 148.0, padding);
        fit =  boundingRect.size.height <= 131;
    } while (!fit && fontSize > minFontSize);
    if (fit) {
        self.textView.font = [self.textView.font fontWithSize:fontSize];
        NSLog(@"Fit!");
    } else {
        NSLog(@"No fit");
    }
    return fit;
}
Antzi
fonte