Height of attributedText not returned - ios

I need to display text in UITableViewCell and need to set the height of tableView. I am using the below methods to calculate the height of row dynamically. But the height is coming out to be a lot more than the actual height.
+ (CGFloat)getquestionLabelHeight:(NSNumber *)width text:(NSString *)text {
CGSize constraint = CGSizeMake([width floatValue], CGFLOAT_MAX);
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGSize boundingBox = [[ZUtility getAttributedText:text].string boundingRectWithSize:constraint options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont fontWithName:#"OpenSans" size:14]} context:context].size;
return boundingBox.height;
}
The reason I am converting text to attributed text is because normal text has html content and for line breaks '&nbsp' is present.
+(NSAttributedString *)getAttributedText:(NSString *)text {
NSMutableAttributedString *attrHTMLText = [[[NSAttributedString alloc] initWithData:[text dataUsingEncoding:NSUTF8StringEncoding] options:#{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,NSCharacterEncodingDocumentAttribute: #(NSUTF8StringEncoding)} documentAttributes:nil error:nil] mutableCopy];
[attrHTMLText addAttribute:NSFontAttributeName value:[UIFont fontWithName:#"OpenSans" size:14.0] range:NSMakeRange(0, attrHTMLText.length)];
[attrHTMLText addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0, attrHTMLText.length)];
return attrHTMLText;
}
For width 343, height is coming out to be 915 which is not accurate.

You can use boundingRectWithSize
NSAttributedString *attrStr = [[NSAttributedString alloc]initWithString:#"<p>The HTML <strong><strong></strong> element defines <strong>strong</strong>text, with added semantic importance.</p>"];
// your attributed string
CGFloat width = 200; // whatever your desired width is
CGRect rect = [attrStr boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];;
NSLog(#"%#",NSStringFromCGRect(rect));

Related

How to know if the word within UILabel was cut

I try to develop autoshrink functionality. I have attributed text, set into a fix-size UILabel. After that I lower the font size and check if the text fits to given container size.
Problem is that UILabel ignores NSLineBreakByWordWrapping if a word is longer than container width. Resulting in I have cut tail word.
Here is the code:
- (void) setCardText:(NSString *)txt {
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:txt];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
[paragraphStyle setAlignment:NSTextAlignmentCenter];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [txt length])];
self.cardLabel.attributedText = attributedString;
for (CGFloat fontSize = 40; fontSize >=5; fontSize--) {
[self.cardLabel setFont:[UIFont fontWithName:#"GothamPro-Light" size:fontSize]];
[paragraphStyle setLineSpacing:fontSize*0.3f];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [txt length])];
self.cardLabel.lineBreakMode = NSLineBreakByWordWrapping;
[self.cardLabel sizeToFit];
if (self.cardLabel.frame.size.width <= 220) {
[self.cardLabel setFrame:CGRectMake(40, 40, 220, self.cardLabel.frame.size.height)];
}
if (self.cardLabel.frame.size.height <= 210) {
[self.cardLabel setFrame:CGRectMake(40, 40, self.cardLabel.frame.size.width, 210)];
}
if (self.cardLabel.frame.size.width <= 220 && self.cardLabel.frame.size.height <= 210) {
[self.cardLabel setFrame:CGRectMake(40, 40, 220, 210)];
break;
}
};
And here is the result (I'm sorry for screenshot in Russian): http://take.ms/kg2mG
In the third line the word is cut and its ending is moved to the next line.
I guess it happens because initially this word doesn't fit to container width and was forcibly broken in half. I suppose I need sort of cut-word-detector, which tells me to keep lowering font size. Or another guess is to oblige the UILabel to be expanded by “unfit word”. But I can't find anything that does this job.
Also I can explode given string into words and check if every single of them fits to container width. But I think this method is a wheel inventing. Is there something I missed that can easily solve my issue?
The method sizeToFit calls sizeThatFits: which returns the ‘best’ size to fit the current bounds and then resize label. So at first you constrain the label and it has to fit the given width. You can see description of NSLineBreakByWordWrapping - Wrapping occurs at word boundaries, unless the word itself doesn’t fit on a single line.
For your purposes you should allow label to fit the knowingly more wider width than it requires. But it is difficult because the task is to find the best font size and we cannot predict the width. And the best way is to find font size based on the longest word in the text.
So the algorithm:
Detect the longest word, by separating by spaces.
Iteratively, decrease the font size and calculate the size of the longest word while the word is bigger than required width.
Set calculated font to full text and call sizeThatFits.
Please, find the sample code below ("Verdana" font was used for testing)
- (void) setText {
NSString * text = #"Incidental, indirect, secondary, side rival - Побочный, косвенный, второстепенный, боковой соперник";
CGFloat maxWidth = 300.;
[self setText:text toLabel:self.label maxWidth:maxWidth];
}
- (void) setText:(NSString *)text
toLabel:(UILabel*)label
maxWidth:(CGFloat)maxWidth
{
CGFloat fontSize = [self fontSizeOfWord:[self theLongestWord:text]
initialFontSize:40.
constrainedByWidth:maxWidth];
NSMutableAttributedString * attributedString = [self attributedStringForText:text];
[self setupAttributedStirng:attributedString withFontWithSize:fontSize];
label.attributedText = attributedString;
CGRect labelFrame = label.frame;
labelFrame.size = [label sizeThatFits:[attributedString sizeAdaptedForWidth:maxWidth]];
label.frame = labelFrame;
}
- (NSString*) theLongestWord:(NSString*)text {
NSArray * words = [text componentsSeparatedByString:#" "];
NSUInteger longestLength = 0;
NSUInteger index = NSNotFound;
for(int i = 0; i < words.count; i++) {
NSString * word = words[i];
CGFloat length = word.length;
if(length > longestLength) {
longestLength = length;
index = i;
}
}
return (index != NSNotFound ? words[index] : nil);
}
- (CGFloat)fontSizeOfWord:(NSString *)word
initialFontSize:(CGFloat)initialFontSize
constrainedByWidth:(CGFloat)maxWidth
{
NSMutableAttributedString * wordString = [self attributedStringForText:word];
CGFloat fontSize = initialFontSize;
for (; fontSize >= 5.; --fontSize) {
[self setupAttributedStirng:wordString
withFontWithSize:fontSize];
CGSize wordSize = [wordString sizeAdaptedForWidth:CGFLOAT_MAX];
if(wordSize.width <= maxWidth){
break;
}
}
return fontSize;
}
- (NSMutableAttributedString*) attributedStringForText:(NSString*)text {
return (text&&text.length ? [[NSMutableAttributedString alloc] initWithString:text]:nil);
}
- (void)setupAttributedStirng:(NSMutableAttributedString *)attributedString
withFontWithSize:(CGFloat)fontSize
{
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
[paragraphStyle setAlignment:NSTextAlignmentCenter];
UIFont * font = [UIFont fontWithName:#"Verdana" size:fontSize];
[paragraphStyle setLineSpacing:fontSize*0.3f];
NSDictionary * attributes = #{NSParagraphStyleAttributeName: paragraphStyle,
NSFontAttributeName: font};
[attributedString addAttributes:attributes
range:NSMakeRange(0, [attributedString length])];
}
Category for NSAttributedString:
#implementation NSAttributedString (AdaptedSize)
- (CGSize) sizeAdaptedForWidth:(CGFloat)width
{
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self);
CGSize targetSize = CGSizeMake(width, CGFLOAT_MAX);
CGSize fitSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter,
CFRangeMake(0, [self length]),
NULL, targetSize, NULL);
CFRelease(framesetter);
return fitSize;
}
#end
Have you tried the UILabel.adjustsFontSizeToWidth property?

Setting dynamic UILabel Height

I have an app with a table view that needs to support dynamic cell heights. In the cell's layoutSubviews method, I am generating a frame for my UILabel controls, which are the only dynamic sized controls in the cells.
For some reason, the width being returned from the following method is less than what it should be and the text gets truncated, but only on short text, such as one word. The width should be maintained as the width that is passed in as the initial frame.
That said, what my method needs to accomplish is adjusting the size of the label to fit all the text while maintaining a preset width.
Here's the code I am using:
- (CGRect)getLabelSizeForText:(NSString*)text withInitialRect:(CGRect)labelFrame andFontSize:(CGFloat)fontSize{
CGSize constrainedSize = CGSizeMake(labelFrame.size.width, MAXFLOAT);
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:[UIFont systemFontOfSize:fontSize], NSFontAttributeName, nil];
CGSize requiredSize = [text boundingRectWithSize:constrainedSize
options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading
attributes:attributesDict context:nil].size;
CGRect adjustedFrameRect = CGRectMake(labelFrame.origin.x, labelFrame.origin.y, requiredSize.width, requiredSize.height);
return adjustedFrameRect;
}
This works for me,
+ (CGSize)textSizeForText:(NSString *)text {
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat width = screenWidth - kPaddingRight;
CGSize maxSize = CGSizeMake(width, CGFLOAT_MAX);
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = 2.5;
NSDictionary *dict = #{NSParagraphStyleAttributeName : paragraphStyle, NSFontAttributeName: [UIFont systemFontOfSize:15] };
[attributedString addAttributes:dict range:NSMakeRange(0, text.length)];
CGRect paragraphRect = [attributedString boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];
return paragraphRect.size;
}

Getting uilabel height for bold attributed text

My code to calculate height required to for label is as follows:
-(float)frameForText:(NSString*)text sizeWithFont:(UIFont*)font constrainedToSize: (float)width{
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
CGRect frame = [text boundingRectWithSize:(CGSize){width, CGFLOAT_MAX} options: (NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) attributes:attributesDictionary
context:nil];
// This contains both height and width, but we really care about height.
return frame.size.height;
}
I have called it as follows from below code to calculate height firs then used it to draw label
//form attributed title
NSString *str_title =#"This is sample title to calculate height";
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[paragraphStyle setLineSpacing: 2.0f];
NSDictionary *attributes = #{ NSFontAttributeName: [UIFont fontWithName:#"PTSans-Bold" size:10], NSParagraphStyleAttributeName: paragraphStyle };
NSAttributedString *attributed_title = [[NSAttributedString alloc] initWithString:str_title attributes:attributes];
//calculate height required for title
float comment_height = [self frameForText:str_title sizeWithFont:[UIFont fontWithName:#"PTSans-Bold" size:10] constrainedToSize:250];
UILabel *lbl_title;
//use calculated height here
lbl_title = [[UILabel alloc] initWithFrame:CGRectMake(60, 5, 250, title_height)];
lbl_title.numberOfLines = 0;
lbl_title.attributedText = attributed_title;
This works fine when font is "PTSans-Regular" and gives exact uilabel height. But, it is not working for "PTSans-Bold" by above code.
How should I return exact UIlabel needed to write "PTSans-Bold" text with label of width 250, font's size 10 and paragraph line spacing equal to 2? Note: the "PTSans-Bold" is not system font, it's font I have added.
Thanks.
This is easyiest way to find UILabel text height dynamically height for below iOS7
CGSize fontSize = [uilabel.text sizeWithFont:uilabel.font];
NSLog(#"height %f",fontSize.height);
for iOS7
float heightIs =[uilabel.text boundingRectWithSize:uilabel.frame.size options:NSStringDrawingUsesLineFragmentOrigin attributes:#{ NSFontAttributeName:uilabel.font } context:nil].size.height;
use the below method after setting font and every properties.
- (CGFloat)getHeight:(UILabel *)label{
CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake( self.bounds.size.width,CGFLOAT_MAX)
options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes: [NSDictionary dictionaryWithObject:label.font
forKey:NSFontAttributeName]
context: nil].size;
return sizeOfText.height;
}
OK, I solved this problem with below code:
-(float)heightOfAttrbuitedText:(NSAttributedString *)attrStr width:(CGFloat )width{
CGRect rect = [attrStr boundingRectWithSize:CGSizeMake(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];
return rect.size.height;
}

Is there a way to get the text height (point perfect not approximate ) for a UITextview in iOS7 (for a chat cell in tableview ) with constant width?

UITextview has a padding of 8 points on each side. Hence I pass 16 points less to the width of the CGRect- height of which I want to find.
As it can be seen in the below function(using sizeWithFont), for iOS6, I get point perfect height.
But for iOS7, the height I get is not accurate when I use the function (using boundingRectWithSize).
#pragma mark - Private methods
- (CGFloat)getTextHeight{
if (isSentMessgae) {
return [_chatMessageModel.message sizeWithFont:[UIFont fontWithName:FONT_TT size:16] constrainedToSize:CGSizeMake(194, FLT_MAX) lineBreakMode:NSLineBreakByWordWrapping].height;
}
return [_chatMessageModel.message sizeWithFont:[UIFont fontWithName:FONT_TT size:16] constrainedToSize:CGSizeMake(154, FLT_MAX) lineBreakMode:NSLineBreakByWordWrapping].height;
}
- (CGFloat)getTextHeightIOS7{
if (isSentMessgae) {
NSString *text = _chatMessageModel.message;
CGFloat width =154;
UIFont *font = [UIFont fontWithName:FONT_TT size:16];
if (isSentMessgae) {
width =194;
}
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:text
attributes:#
{
NSFontAttributeName: font
}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
DebugLog("text heigh : %f",rect.size.height);
return (rect.size.height);
}
NSString *text = _chatMessageModel.message;
CGFloat width =154;
UIFont *font = [UIFont fontWithName:FONT_TT size:16];
if (isSentMessgae) {
width =194;
}
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:text
attributes:#
{
NSFontAttributeName: font
}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
// DebugLog("text heigh : %f",rect.size.height);
return (rect.size.height ));
}

Replacement for deprecated -sizeWithFont:constrainedToSize:lineBreakMode: in iOS 7?

In iOS 7, the method:
- (CGSize)sizeWithFont:(UIFont *)font
constrainedToSize:(CGSize)size
lineBreakMode:(NSLineBreakMode)lineBreakMode
and the method:
- (CGSize)sizeWithFont:(UIFont *)font
are deprecated. How can I replace
CGSize size = [string sizeWithFont:font
constrainedToSize:constrainSize
lineBreakMode:NSLineBreakByWordWrapping];
and:
CGSize size = [string sizeWithFont:font];
You could try this:
CGRect textRect = [text boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:FONT}
context:nil];
CGSize size = textRect.size;
Just change "FONT" for an "[UIFont font....]"
As we cant use sizeWithAttributes for all iOS greater than 4.3 we have to write conditional code for 7.0 and previous iOS.
1) Solution 1:
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0")) {
CGSize size = CGSizeMake(230,9999);
CGRect textRect = [specialityObj.name
boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont fontWithName:[AppHandlers zHandler].fontName size:14]}
context:nil];
total_height = total_height + textRect.size.height;
}
else {
CGSize maximumLabelSize = CGSizeMake(230,9999);
expectedLabelSize = [specialityObj.name sizeWithFont:[UIFont fontWithName:[AppHandlers zHandler].fontName size:14] constrainedToSize:maximumLabelSize lineBreakMode:UILineBreakModeWordWrap]; //iOS 6 and previous.
total_height = total_height + expectedLabelSize.height;
}
2) Solution 2
UILabel *gettingSizeLabel = [[UILabel alloc] init];
gettingSizeLabel.font = [UIFont fontWithName:[AppHandlers zHandler].fontName size:16]; // Your Font-style whatever you want to use.
gettingSizeLabel.text = #"YOUR TEXT HERE";
gettingSizeLabel.numberOfLines = 0;
CGSize maximumLabelSize = CGSizeMake(310, 9999); // this width will be as per your requirement
CGSize expectedSize = [gettingSizeLabel sizeThatFits:maximumLabelSize];
The first solution is sometime fail to return proper value of height. so use another solution. which will work perfectly.
The second option is quite well and working smoothly in all iOS without conditional code.
Here is simple solution :
Requirements :
CGSize maximumSize = CGSizeMake(widthHere, MAXFLOAT);
UIFont *font = [UIFont systemFontOfSize:sizeHere];
Now As constrainedToSizeusage:lineBreakMode: usage is deprecated in iOS 7.0:
CGSize expectedSize = [stringHere sizeWithFont:font constrainedToSize:maximumSize lineBreakMode:NSLineBreakByWordWrapping];
Now usage in greater version of iOS 7.0 will be:
// Let's make an NSAttributedString first
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:stringHere];
//Add LineBreakMode
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
[attributedString setAttributes:#{NSParagraphStyleAttributeName:paragraphStyle} range:NSMakeRange(0, attributedString.length)];
// Add Font
[attributedString setAttributes:#{NSFontAttributeName:font} range:NSMakeRange(0, attributedString.length)];
//Now let's make the Bounding Rect
CGSize expectedSize = [attributedString boundingRectWithSize:maximumSize options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
Below are two simple methods that will replace these two deprecated methods.
And here are the relevant references:
If you are using NSLineBreakByWordWrapping, you don't need to specify the NSParagraphStyle, as that is the default:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSParagraphStyle_Class/index.html#//apple_ref/occ/clm/NSParagraphStyle/defaultParagraphStyle
You must get the ceil of the size, to match the deprecated methods' results.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/NSString_UIKit_Additions/#//apple_ref/occ/instm/NSString/boundingRectWithSize:options:attributes:context:
+ (CGSize)text:(NSString*)text sizeWithFont:(UIFont*)font {
CGSize size = [text sizeWithAttributes:#{NSFontAttributeName: font}];
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
+ (CGSize)text:(NSString*)text sizeWithFont:(UIFont*)font constrainedToSize:(CGSize)size{
CGRect textRect = [text boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName: font}
context:nil];
return CGSizeMake(ceilf(textRect.size.width), ceilf(textRect.size.height));
}
In most cases I used the method sizeWithFont:constrainedToSize:lineBreakMode: to estimate the minimum size for a UILabel to accomodate its text (especially when the label has to be placed inside a UITableViewCell)...
...If this is exactly your situation you can simpy use the method:
CGSize size = [myLabel textRectForBounds:myLabel.frame limitedToNumberOfLines:mylabel.numberOfLines].size;
Hope this might help.
UIFont *font = [UIFont boldSystemFontOfSize:16];
CGRect new = [string boundingRectWithSize:CGSizeMake(200, 300) options:NSStringDrawingUsesFontLeading attributes:#{NSFontAttributeName: font} context:nil];
CGSize stringSize= new.size;
[Accepted answer works nicely in a category. I'm overwriting the deprecated method names. Is this a good idea? Seems to work with no complaints in Xcode 6.x]
This works if your Deployment Target is 7.0 or greater. Category is NSString (Util)
NSString+Util.h
- (CGSize)sizeWithFont:(UIFont *) font;
- (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size;
NSString+Util.m
- (CGSize)sizeWithFont:(UIFont *) font {
NSDictionary *fontAsAttributes = #{NSFontAttributeName:font};
return [self sizeWithAttributes:fontAsAttributes];
}
- (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
NSDictionary *fontAsAttributes = #{NSFontAttributeName:font};
CGRect retVal = [self boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:fontAsAttributes context:nil];
return retVal.size;
}
UIFont *font = [UIFont fontWithName:#"Courier" size:16.0f];
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
paragraphStyle.alignment = NSTextAlignmentRight;
NSDictionary *attributes = #{ NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle };
CGRect textRect = [text boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
CGSize size = textRect.size;
from two answers 1 + 2

Resources