objective c - boundingRectWithSize para NSAttributedString que devuelve un tamaño incorrecto
objective-c (21)
Estoy tratando de obtener el rect para una cadena atribuida, pero la llamada boundingRectWithSize no respeta el tamaño que paso y está devolviendo un rect con una sola línea de altura en lugar de una gran altura (es una cadena larga). Experimenté pasando un valor muy grande para la altura y también 0 como en el siguiente código, pero el rect devuelto es siempre el mismo.
CGRect paragraphRect = [attributedText boundingRectWithSize:CGSizeMake(300,0.0)
options:NSStringDrawingUsesDeviceMetrics
context:nil];
¿Esto está roto, o tengo que hacer algo más para que se devuelva un rect para texto envuelto?
¡Resulta que CADA parte de un NSAttributedString debe tener un diccionario establecido con al menos NSFontAttributeName y NSForegroundColorAttributeName set, si desea que boundingRectWithSize funcione realmente!
No veo eso documentado en ningún lado.
@warrenm Perdón por decir que el método framesetter no funcionó para mí.
Obtuve esto. Esta función puede ayudarnos a determinar el tamaño de fotograma necesario para un rango de cadenas de un NSAttributedString en iphone / Ipad SDK para un ancho determinado:
Se puede usar para una altura dinámica de las 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
Descubrí que la solución preferida no maneja 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;
Ed McManus ciertamente ha proporcionado la 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. Observe que anotherString (que se agrega a la cadena ) se inicializó sin un diccionario de atributos. Este es un inicializador legítimo para otroString pero boundingRectWithSize: no da un tamaño preciso en este caso.
En caso de que desee obtener un cuadro delimitador al truncar la cola, esta pregunta puede serle útil.
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];
Ese método parece defectuoso de muchas maneras. Por un lado, como nota, no respeta las restricciones de ancho. Por otro lado, lo he visto NSObject
porque parece suponer que todos los atributos son de tipo NSObject
(por ejemplo, intentó pasar _isDefaultFace
a un CTFontRef
). También se bloqueará a veces cuando se proporcione un contexto de dibujo de cadena porque intenta agregar un atributo de valor nulo a una cadena atribuible mutable detrás de las escenas.
Te animo a que evites este método por completo. Puede usar el Texto principal directamente para estimar el tamaño de la cadena, si puede manejar la sobrecarga de crear un fotocohetero para cada cadena que necesita dibujar. Tampoco respeta las restricciones de ancho, pero parece que se encuentra dentro de unos pocos píxeles, según mi experiencia.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attrString);
CGSize targetSize = CGSizeMake(320, CGFLOAT_MAX);
CGSize fitSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [attrString length]), NULL, targetSize, NULL);
CFRelease(framesetter);
He tenido el mismo problema al no obtener un tamaño preciso con estas técnicas y he cambiado mi enfoque para que funcione.
Tengo una cadena larga atribuida que he intentado encajar en una vista de desplazamiento para que se muestre correctamente sin ser truncada. Lo que hice para que el texto funcionara de manera confiable fue no establecer la altura en absoluto como una restricción y, en cambio, permití que el tamaño intrínseco tomara el relevo. Ahora el texto se muestra correctamente sin truncar 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.
Llego un poco tarde al juego, pero he estado tratando de encontrar la forma de encontrar el cuadro delimitador que se ajuste a una cadena atribuida para hacer que un anillo de enfoque suene como 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. boundingRectWithSize
falla miserablemente para esto y para CTFramesetterCreateWithAttributedString
.
Usando un NSLayoutManager
el siguiente código parece ser 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 devuelto. El siguiente código utiliza el layoutManager desde un NSTextView
.
NSLayoutManager* layout = [self layoutManager];
NSTextContainer* container = [self textContainer];
CGRect focusRingFrame = [layout boundingRectForGlyphRange:NSMakeRange(0, [[self textStorage] length]) inTextContainer:container];
Me gustaría agregar mis pensamientos ya que tenía exactamente el mismo problema.
Estaba usando UITextView
ya que tenía una alineación de texto más agradable (justificar, que en ese momento no estaba disponible en UILabel
), pero para "simular" UILabel
no interactivo no desplazable, apagaba completamente el desplazamiento, el rebote, y la interacción del usuario.
Por supuesto, el problema era que el texto era dinámico, y mientras que el ancho sería fijo, la altura debía recalcularse cada vez que establecía un nuevo valor de texto.
boundingRectWithSize
no funcionó bien para mí, por lo que pude ver, UITextView
estaba agregando un margen en la parte superior que boundingRectWithSize
no entraría en un recuento, por lo tanto, la altura recuperada de boundingRectWithSize
era 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 más, he decidido seguir este 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 copió directamente de mi fuente, tuve que ajustarlo / borrarlo de muchas otras cosas que no son necesarias para este artículo. No lo tomes para copiar-pegar-y-quieres-trabajar-codificar.
La desventaja evidente es que tiene alloc y release, para cada llamada.
Pero, la ventaja es que evita depender de la compatibilidad entre cómo boundingRectWithSize dibuja texto y calcula su tamaño y la implementación del dibujo de texto en UITextView
(o UILabel
que también puede usar simplemente reemplace UITextView
con UILabel
). Cualquier "error" que pueda tener Apple se evita de esta manera.
PS: parece que no se necesita este UITextView
"temporal" y solo se puede pedir a sizeThatFits
directamente desde el destino, sin embargo, eso no funcionó para mí. Aunque la lógica diría que debería funcionar y no se necesita alloc / release de UITextView
temporal, no lo hizo. Pero esta solución funcionó perfectamente para cualquier texto que pudiera establecer.
Mi decisión final después de una larga investigación:
- boundingRectWithSize
función - boundingRectWithSize
devuelve el tamaño correcto para una secuencia ininterrumpida de caracteres solamente! En caso de que la cadena contenga espacios o algo más (llamado 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 superponerse a 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 CGRect
devuelto puede exceder el valor de ancho del parámetro de tamaño ".
Por lo tanto, es necesario encontrar otra forma de calcular rect real ...
¡Después de una solución de proceso de investigación larga finalmente encontrada !!! No estoy seguro de que funcione bien para todos los casos relacionados con UITextView
, ¡pero se detectó algo importante e importante!
boundingRectWithSize
función boundingRectWithSize
así como CTFramesetterSuggestFrameSizeWithConstraints
(y muchos otros métodos) calcularán el tamaño y la porción de texto correcta cuando se use el rectángulo correcto. Por ejemplo, UITextView
tiene textView.bounds.size.width
y este valor no es un rectángulo real utilizado por el sistema cuando se dibuja texto en UITextView
.
Encontré parámetros muy interesantes y realicé cálculos simples en código:
CGFloat padding = textView.textContainer.lineFragmentPadding;
CGFloat actualPageWidth = textView.bounds.size.width - padding * 2;
Y la magia funciona - ¡todos mis textos son correctos ahora! ¡Disfrutar!
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 manejaba bien el dibujo, así que lo busqué 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 una UITextView solo para llamar a -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);
Ok, así que pasé mucho tiempo depurando esto. Descubrí que la altura máxima de texto definida por boundingRectWithSize
permitida para mostrar texto por mi UITextView
era menor que el tamaño del marco.
En mi caso, el marco es a lo sumo 140pt, pero el UITextView tolera textos a lo sumo 131pt.
Tuve que resolverlo 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;
}
Parece que no ofreciste 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á envoltura de línea, por lo tanto, asegúrese de que el tamaño encuadernado sea correcto, de lo contrario, obtendrá resultados incorrectos.
Por algún motivo, 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 usar boundingRectWithSize, cree un UITextView, con un marco aleatorio, y llame su tamañoThatFits 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 autorrelease, ya que habrá n número de UITextView creados, la memoria de tiempo de ejecución de la aplicación aumentará si no usamos autoreleasepool.
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;
}
Una cosa que estaba notando es que el rect que vendría de (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context
tendría un ancho mayor que Pasé. Cuando esto sucedió, mi cuerda quedaría truncada. 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;
Swift tres versiones
let string = "A great test string."
let font = UIFont.systemFont(ofSize: 14)
let attributes: [String: Any] = [NSFontAttributeName: 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 = (alert.alertDescription 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
Medir el texto con CTFramesetter funciona mejor ya que proporciona tamaños enteros y maneja bien los emoji y otros caracteres unicode.
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!
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.
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 UILabel que no estaba formateando correctamente. En realidad, establecer el texto atribuido en la etiqueta finalmente solucionó el problema.
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];