boundingRectWithSize para NSAttributedString devuelve un tamaño incorrecto

151

Estoy tratando de obtener el rect para una cadena atribuida, pero la llamada boundingRectWithSize no respeta el tamaño que paso y devuelve un rect con una altura de línea única en lugar de una altura grande (es una cadena larga). He experimentado pasando un valor muy grande para la altura y también 0 como en el código a continuación, pero el rect devuelto es siempre el mismo.

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

¿Está roto o necesito hacer algo más para que devuelva un rect para el texto envuelto?

RunLoop
fuente
55
¿Tienes un estilo de párrafo con truncamiento / recorte lineBreakMode?
danyowdee
1
si está leyendo esto porque UILabel mide / ajusta a un ancho incorrecto eche un vistazo a stackoverflow.com/questions/46200027/… . específicamente, establecer NSAllowsDefaultLineBreakStrategy en falso en el inicio de la aplicación.
eric

Respuestas:

312

Parece que no estabas proporcionando las opciones correctas. Para envolver etiquetas, proporcione al menos:

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

Nota: si el ancho del texto original es inferior a 300.f, no habrá ajuste de línea, así que asegúrese de que el tamaño del límite sea correcto, de lo contrario, obtendrá resultados incorrectos.

Ed McManus
fuente
25
Todos necesitan ver esta respuesta, porque funciona. Quizás se necesita documentación más clara para este método y las cadenas atribuidas en general; No es obvio dónde debe buscar.
macserv
31
¡Atención! ¡No siempre funciona! A veces, el ancho devuelto es mayor que el ancho del parámetro de tamaño. Incluso la documentación del método de Apple indica: "Las restricciones que especifique en el parámetro de tamaño son una guía para el procesador sobre cómo dimensionar la cadena. Sin embargo, el rectángulo delimitador real devuelto por este método puede ser mayor que las restricciones si se necesita espacio adicional para renderizar la cadena completa ".
Klaas
75
La API para NSAttributedString y boundingRectWithSize es absolutamente impactante.
malhal
27
Otro problema es que la altura (y presumiblemente el ancho) devuelta en el párrafoRect es casi siempre un valor fraccionario. Entonces puede salir diciendo 141.3 como la altura. Debe usar el resultado de ceilf(paragraphRect.size.height)para que se redondee. Me olvido de esto todo el tiempo y me pregunto por qué mis etiquetas todavía están recortadas.
jamone
39
Siempre boundingRectWithSize:envuelvo mis cálculos en CGRectIntegral () que CGRectIntegral rounds the rectangle’s origin downward and its size upward to the nearest whole integers, en este caso, redondeará la altura y el ancho para garantizar que no se produzca recorte si la altura o el ancho son un valor fraccional.
runmad
47

Por alguna razón, boundingRectWithSize siempre devuelve un tamaño incorrecto. Descubrí una solución. Hay un método para UItextView -sizeThatFits que devuelve el tamaño adecuado para el conjunto de texto. Entonces, en lugar de utilizar boundingRectWithSize, cree un UITextView, con un marco aleatorio, y llame a su sizeThatFits con el ancho respectivo y la altura CGFLOAT_MAX. Devuelve el tamaño que tendrá la altura adecuada.

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

Si está calculando el tamaño en un ciclo while, no olvide agregarlo en un grupo de liberación automática, ya que habrá un número de UITextView creado, la memoria de tiempo de ejecución de la aplicación aumentará si no utilizamos la liberación automática.

shoan
fuente
1
Esto funciona. Las otras formas en que lo intenté nunca pudieron llegar al trabajo. Creo que mi problema proviene de la complejidad de la cadena atribuida que estaba asignando. Provenía de un archivo RTF y usaba una variedad de fuentes, interlineado, incluso sombras, y a pesar de usar UsesLineFragmentOrigin y UsesFontLeading, nunca pude obtener un resultado que fuera lo suficientemente alto, y el problema era peor cuanto más tiempo se alargaba la cadena atribuida. Supongo que para cadenas relativamente simples, el método boundingRectWithSize puede funcionar. Realmente necesitan un mejor método para trabajar con esto, en mi opinión.
John Bushnell
¿No crees que es excesivo crear UITextViewtantas veces como heightForRowAtIndexPathse llama al método? toma tiempo
János
Estoy de acuerdo contigo @ János, pero esta fue la única solución que funcionó para mí.
shoan
por cierto, pedí la solución adecuada: stackoverflow.com/questions/32495744/…
János
Esta es la única solución que funciona. Este problema tiene 9 años y Apple aparentemente no quiere resolver esto o sus ingenieros son débiles para encontrar una solución para esto. Estamos hablando de iOS 11, que todavía tiene el mismo error.
Pato
31

Ed McManus ciertamente ha proporcionado una clave para que esto funcione. Encontré un caso que no 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 no tendrá la altura correcta. Tenga en cuenta que anotherString (que se adjunta a la cadena ) se ha inicializado sin un diccionario de atributos. Este es un inicializador legítimo para anotherString pero boundingRectWithSize: no proporciona un tamaño exacto en este caso.

Paul Heller
fuente
33
¡Gracias! Resulta que CADA parte de una NSAttributedString debe tener un diccionario establecido con al menos NSFontAttributeName y NSForegroundColorAttributeName establecido, si desea que limitingRectWithSize realmente funcione. No veo eso documentado en ningún lado.
Ben Wheeler
3
Tenga en cuenta que también parece que las diferentes NSAttributedStrings que se combinan para formar una sola deben usar el mismo diccionario (y, por lo tanto, la misma fuente) para queINDINGRectWithSize funcione correctamente.
Ben Wheeler
1
Esta solución es muy similar a la que uso para mi categoría UILabel para "reducir para encajar". La clave para que esto funcionara fue crear el rectángulo delimitador con altura FLT_MAX. Sin eso, parecería obtener un rectángulo para una sola línea.
dre1138
+ 1s todo el año. Esto realmente me atrapó, así que gracias por resolverlo chicos.
Wex
Tuve este problema Usé espacios y líneas nuevas NSMutableAttributedStringsin atributos y estaba obteniendo un tamaño incorrecto.
vbezhenar 23/03/2016
28

Mi decisión final después de una larga investigación: ¡la
- boundingRectWithSizefunción devuelve el tamaño correcto solo para la secuencia ininterrumpida de caracteres! En caso de que la cadena contenga espacios u otra cosa (llamada por Apple "Algunos de los glifos"), ¡es imposible obtener el tamaño real de rect necesario para mostrar el texto!
He reemplazado espacios en mis cadenas por letras e inmediatamente obtuve el resultado correcto.

Apple dice aquí: https://developer.apple.com/documentation/foundation/nsstring/1524729-boundingrectwithsize

"Este método devuelve los límites reales de los glifos en la cadena. Algunos de los glifos (espacios, por ejemplo) pueden solapar las restricciones de diseño especificadas por el tamaño pasado, por lo que en algunos casos el valor de ancho del componente de tamaño de el valor devuelto CGRectpuede exceder el valor del ancho del parámetro de tamaño ".

Por lo tanto, es necesario encontrar otra forma de calcular el rect real ...


Después de un largo proceso de investigación, la solución finalmente se encontró. No estoy seguro de que funcione bien para todos los casos relacionados UITextView, ¡pero se detectó lo principal e importante!

boundingRectWithSizeLa función así como CTFramesetterSuggestFrameSizeWithConstraints(y muchos otros métodos) calcularán el tamaño y la porción de texto correcta cuando se usa el rectángulo correcto. Por ejemplo, UITextViewtiene textView.bounds.size.width- y este valor no es un rectángulo real utilizado por el sistema cuando se dibuja texto UITextView.

Encontré un parámetro muy interesante y realicé un cálculo simple en el código:

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

Y la magia funciona: ¡todos mis textos calculados correctamente ahora! ¡Disfrutar!

Юрий Сталоверов
fuente
1
Sí, también me di cuenta de que no mantiene las restricciones de ancho, siempre sale más grande. Eso no es realmente genial, desaprobaron el método de trabajo y ahora tenemos que lidiar con esta basura.
Boris Gafurov
1
Usted señor, es mi nuevo héroe! Esto me estaba volviendo loco. Estoy configurando las inserciones textContainers, por lo que tuve que usar textView.textContainer.size.width en lugar de textView.bounds.size.witdh.
GCBenson
No podría votar lo suficiente. Tan extraño que los espacios no se consideran en los cálculos delimitadores.
Chase Holland
1
Reemplazar los espacios con algún personaje ficticio como "3" devuelve la altura exacta
Abuzar Amin
1
Esto salvó mi carrera y mi relación con el equipo de control de calidad. ¡Te debo un cervecero!
lucaslt89
14

Swift cuatro versiones

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

La medición del texto con CTFramesetter funciona mejor ya que proporciona tamaños enteros y maneja bien los emoji y otros caracteres unicode.

David Rees
fuente
10

No tuve suerte con ninguna de estas sugerencias. Mi cadena contenía viñetas Unicode y sospecho que estaban causando dolor en el cálculo. Noté que UITextView estaba manejando bien el dibujo, así que busqué eso para aprovechar su cálculo. Hice lo siguiente, que probablemente no sea tan óptimo como los métodos de dibujo NSString, pero al menos es preciso. También es un poco más óptimo que inicializar un UITextView solo para llamar -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
fuente
Puede que no hayan sido los puntos Unicode, pueden haber sido espacios al final de su cadena. Vea la respuesta anterior: stackoverflow.com/a/25941139/337934 .
Sábado
9

En caso de que desee obtener un cuadro delimitador truncando la cola, esta pregunta puede ayudarlo.

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.
fuente
9

Resulta que CADA parte de una NSAttributedString debe tener un diccionario establecido con al menos NSFontAttributeName y NSForegroundColorAttributeName establecido, si desea que limitingRectWithSize realmente funcione.

No veo eso documentado en ningún lado.

Ben Wheeler
fuente
Gracias, esta fue la solución para mí, excepto que encontré que solo NSFontAttributeName era necesario, no NSForegroundColorAttributeName
Marmoy
7

@warrenm Lamento decir que el método de creación de marcos no funcionó para mí.

Obtuve esto. Esta función puede ayudarnos a determinar el tamaño de fotograma necesario para un rango de cadena de un NSAttributedString en iphone / Ipad SDK para un ancho dado:

Se puede usar para una altura dinámica de celdas 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));
}

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

Karan Alangat
fuente
No funciona para mí en ningún dispositivo (iPhone 4 y 5). Tanto en el dispositivo con iOS7.1.2 como en 4s Simulator con iOS8.3 :(
sanjana
Esto necesita alguna importación?
jose920405
@KaranAlangat sí#import <CoreText/CoreText.h>
jose920405
7

Descubrí que la solución preferida no maneja los saltos de línea.

He encontrado que este enfoque funciona en todos los 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
fuente
En mi caso, debo hacer el cálculo en el subproceso de fondo, y el uso de elementos de la interfaz de usuario no está permitido
Lubbo
4

He tenido el mismo problema al no obtener un tamaño exacto utilizando estas técnicas y he cambiado mi enfoque para que funcione.

Tengo una cadena larga atribuida que he estado tratando de ajustar en una vista de desplazamiento para que se muestre correctamente sin truncarse. Lo que hice para que el texto funcionara de manera confiable fue no establecer la altura como una restricción y, en cambio, permití que el tamaño intrínseco se hiciera cargo. Ahora el texto se muestra correctamente sin ser truncado y no tengo que calcular la altura.

Supongo que si tuviera que obtener la altura de manera confiable, crearía una vista que está oculta y estas restricciones y obtendría la altura del marco una vez que se apliquen las restricciones.

Brennan
fuente
2
¿Puedes agregar una muestra de código para demostrar esto? Gracias.
Nathan Buggia
3

Llegué un poco tarde al juego, pero he estado tratando de encontrar una manera que funcione para encontrar el cuadro delimitador que se ajuste alrededor de una cadena atribuida para hacer un anillo de enfoque como lo hace editar un archivo en Finder. todo lo que había intentado falló cuando hay espacios al final de la cadena o múltiples espacios dentro de la cadena. boundingRectWithSizefalla miserablemente por esto y por eso CTFramesetterCreateWithAttributedString.

El uso NSLayoutManagerdel siguiente código parece hacer el truco en todos los casos que he encontrado hasta ahora y devuelve un rect que limita perfectamente la cadena. Bonificación: si selecciona el texto, los bordes de la selección van hasta los límites del rectángulo devuelto. El siguiente código utiliza el 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
fuente
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 en todas las fuentes perfectamente!

Dmitry Coolerov
fuente
1

Tuve el mismo problema, pero reconocí que la altura restringida se ha configurado correctamente. Entonces hice lo siguiente:

-(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
fuente
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);

Intenté todo en esta página y todavía tenía un caso para un UILabel que no se formateaba correctamente. En realidad, establecer el texto atribuido en la etiqueta finalmente solucionó el problema.

Pequeño
fuente
1

Una cosa que noté es que el rectángulo que regresaría (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)contexttendría un ancho mayor que el que pasé. Cuando esto sucediera, mi cadena se truncaría. Lo resolví así:

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 un poco más de contexto; Tenía texto de varias líneas e intentaba encontrar la altura correcta para mostrarlo. BoundRectWithSize a veces devolvía un ancho mayor que el que especificaría, por lo tanto, cuando utilicé mi pasado en ancho y la altura calculada para mostrar mi texto, se truncaría Desde la prueba, cuando la funciónINDINGRectWithSize utilizaba el ancho incorrecto, la cantidad por la que acortaría la altura era de 1 línea. Entonces verificaría si el ancho era mayor y, de ser así, agregaría lineHeight de la fuente para proporcionar suficiente espacio para evitar el truncamiento.

Odyth
fuente
¿Cómo afecta la altura de la línea con el ancho?
Roi Mulia
La altura de la línea @RoiMulia no afecta el ancho. Actualicé mi respuesta para proporcionar más contexto sobre cómo esto solucionó mi error.
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;

Si el marco de la etiqueta no agrega 10, ¡este método nunca funcionará! ¡Espero que esto le pueda ayudar! buena suerte.

MICHEAL LV
fuente
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
fuente
En mi caso, debo hacer el cálculo en el subproceso de fondo, y el uso de elementos de la interfaz de usuario no está permitido
Lubbo
0

Me gustaría agregar mis pensamientos ya que tenía exactamente el mismo problema.

Estaba usando UITextViewporque tenía una mejor alineación de texto (justificar, que en ese momento no estaba disponible UILabel), pero para "simular" no interactivo-no desplazable UILabel, desactivaba el desplazamiento completo, el rebote y la interacción del usuario .

Por supuesto, el problema era que el texto era dinámico, y aunque el ancho sería fijo, la altura debería recalcularse cada vez que estableciera un nuevo valor de texto.

boundingRectWithSizeno me funcionó bien, por lo que pude ver, UITextViewfue agregar un margen en la parte superior que boundingRectWithSizeno entraría en un conteo, por lo tanto, la altura recuperada boundingRectWithSizefue menor de lo que debería ser.

Como el texto no debía actualizarse rápidamente, solo se usa para obtener información que puede actualizarse cada 2-3 segundos como máximo, he decidido seguir el siguiente enfoque:

/* 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 );
}

* El código anterior no se copia directamente de mi fuente, tuve que ajustarlo / borrarlo de un montón de otras cosas que no son necesarias para este artículo. No lo tome por copiar-pegar-y-va a funcionar-código.

La desventaja obvia es que tiene asignación y liberación, para cada llamada.

Pero, la ventaja es que evita depender de la compatibilidad entre la forma en que boundingRectWithSize dibuja el texto y calcula su tamaño y la implementación del dibujo de texto UITextView(o UILabelque también puede usar simplemente reemplazar UITextViewcon UILabel). De esta manera, se evitan los "errores" que Apple pueda tener.

PD: Parece que no deberías necesitar esta "temperatura" UITextViewy puedes pedir sizeThatFitsdirectamente desde el objetivo, sin embargo, eso no funcionó para mí. Aunque la lógica diría que debería funcionar y la asignación / liberación de temporal UITextViewno es necesaria, no lo hizo. Pero esta solución funcionó a la perfección para cualquier texto que agregue.

Sinisa
fuente
En mi caso, debo hacer el cálculo en el subproceso de fondo, y el uso de elementos de la interfaz de usuario no está permitido
Lubbo
0

Ok, así que pasé mucho tiempo depurando esto. Descubrí que la altura máxima del texto según lo definido por mi boundingRectWithSizepermite que el texto sea UITextViewmás bajo que el tamaño del marco.

En mi caso, el marco es como máximo 140 puntos, pero el UITextView tolera textos como máximo 131 puntos.

Tuve que resolver eso manualmente y codificar la altura máxima "real".

Aquí está mi solución:

- (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
fuente