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?
Related
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 ' ' 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));
I have a method used to calculate the exact height size for a text container. The method returns not correct values when trying to measure the height of an attributed string filled with arabic html content.
This is the code used :
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString *)text andWidth:(CGFloat)width
{
NSTextStorage *textStorager = [[NSTextStorage alloc] initWithAttributedString:text];
NSTextContainer *textContainerr = [[NSTextContainer alloc] init];
textContainerr.size = CGSizeMake(width, FLT_MAX);
NSLayoutManager *layoutManagerr = [[NSLayoutManager alloc] init];
[layoutManagerr addTextContainer:textContainerr];
[textStorager addLayoutManager:layoutManagerr];
[textStorager addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:self.fontSize] range:NSMakeRange(0, [textStorager length])];
[textContainerr setLineFragmentPadding:5.0];
layoutManagerr.allowsNonContiguousLayout = NO;
CGFloat size = [layoutManagerr usedRectForTextContainer:textContainerr].size.height;
NSLog(#"size: %f - height_TextView:%f",size,height_TextView);
return size;
}
Is there any solution for measuring the exact height of any html content ?
Thanks
It´s a pretty basic problem but I couldn´t find a proper solution for it. I have several circles which have text in it like you can see in the picture. The text gets loaded dynamically and has a size from one word up to five words or more. The goal is to put the text as big as possible into the circle. New lines can appear but every individual word should stay together. The example image is kind of ok but I would prefer the text to be bigger because there is still some free space between the text and the circle. The circle is 80x80. All solution I tried cropped the text strangly or the text is too small.
How I create the label:
UILabel *buttonlabel = [[UILabel alloc] initWithFrame:CGRectMake(12,7,57,64)];
[buttonlabel setText: #"Recipes"];
buttonlabel.font = [UIFont fontWithName:#"HelveticaNeue-Light" size:18.0f];
buttonlabel.textColor = [UIColor whiteColor];
buttonlabel.textAlignment = NSTextAlignmentCenter;
buttonlabel.lineBreakMode = NSLineBreakByWordWrapping;
buttonlabel.numberOfLines = 3;
[button addSubview:buttonlabel];
[buttonlabel release];
EDIT:
So I tried the solution of Rufel. I think the shrinking kind of works but my words get ripped apart. Even though I have buttonlabel.lineBreakMode = NSLineBreakByWordWrapping;
It looks like this:
This is my code. I also implemented the other methods mentioned in an answer.
//Create the button labels
UILabel *buttonlabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 60, 60)];
[buttonlabel setText: #"text";
buttonlabel.textColor = [UIColor whiteColor];
buttonlabel.textAlignment = NSTextAlignmentCenter;
buttonlabel.lineBreakMode = NSLineBreakByWordWrapping;
buttonlabel.numberOfLines = 0;
CGFloat fontSize = 20; // The max font size you want to use
CGFloat labelHeightWithFont = 0;
UIFont *labelFont = nil;
do {
// Trying the current font size if it fits
labelFont = [UIFont systemFontOfSize:fontSize--];
CGRect boundingRect = [self boundingRectForString:subcatbuttontitlesarray[buttonTag-1] font:labelFont];
labelHeightWithFont = boundingRect.size.height;
// Loop until the text at the current size fits the maximum width/height.
} while (labelHeightWithFont > [self buttonLabelMaxWidth]);
buttonlabel.text = subcatbuttontitlesarray[buttonTag-1];
buttonlabel.font = labelFont;
- (CGRect)boundingRectForString:(NSString *)string font:(UIFont *)font
{
return [string boundingRectWithSize:CGSizeMake([self buttonLabelMaxWidth], MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:#{NSFontAttributeName: font}
context:nil];
}
- (CGFloat)buttonLabelMaxWidth
{
CGFloat hypotenuse = CGRectGetWidth(CGRectMake(0, 0, 60, 60));
CGFloat rightTriangleCathetus = sqrtf((hypotenuse*hypotenuse)/2);
return rightTriangleCathetus;
}
I found this thread here:
iOS7 - Adjusting font size of multiline label to fit its frame
which has the same problem.
Edit 2:
After searching a complete day for the solution and trying all kinds of combinations of the label attributes I somehow figured out that the "numberoflines" is my culprit. So I came up with this dumb solution of counting the words in the string and adjust the number of lines based on the numbers of the string:
NSString *samplestring = #"Three words string";
//Count the words in this string
int times = [[samplestring componentsSeparatedByString:#" "] count]-1;
UILabel *testlabel = [[UILabel alloc]initWithFrame:CGRectMake(30, 30, 60, 60)];
[testlabel setText:samplestring];
[testlabel setFont:[UIFont fontWithName:#"HelveticaNeue-UltraLight" size:40.0f]];
[testlabel setBackgroundColor:[UIColor redColor]];
[testlabel setAdjustsFontSizeToFitWidth:YES];
[testlabel setTextAlignment:NSTextAlignmentCenter];
//My workaround
if(times ==0){
[testlabel setNumberOfLines:1];
}else{
if(times==1){
[testlabel setNumberOfLines:2];
}
else{
[testlabel setNumberOfLines:3];
}}
[self.view addSubview:testlabel];
What you want to do, I think, is to ask the NSString for its boundingRectWithSize:options:attributes:context:. By setting the width of the bounding rect, you can find out what the height would be. You can use the parametric formula for the circle to determine whether that bounding rect fits entirely within the center of the circle. Unfortunately you will have to perform a kind of trial-and-error sequence of approximations, where the text gets larger and larger until the top and bottom stick out of the circle, and then narrow the proposed width and see whether this causes the height to grow too much because the text now wraps an extra time.
Say you have a custom view in which you draw a circle that fits its frame (80x80 in your example).
You will first want to find the maximum width your label can take without letters crossing the circle:
- (CGFloat)buttonLabelMaxWidth
{
CGFloat hypotenuse = CGRectGetWidth(self.bounds);
CGFloat rightTriangleCathetus = sqrtf((hypotenuse*hypotenuse)/2);
return floorf(rightTriangleCathetus);
}
Next, when you pass the title to display, you will want to iterate by decreasing an initially oversized font until the resulting string boundary fits the width previously calculated (which is also the maximum height since it's a circle). UPDATE: You will also want to check every words in the title to be sure they are not being truncated (that they fit the maximum width).
- (void)setButtonTitle:(NSString *)title
{
CGFloat fontSize = 20; // The max font size you want to use
CGFloat minimumFontSize = 5; // The min font size you want to use
CGFloat labelHeightWithFont = 0;
CGFloat longestWordWidth = 0;
UIFont *labelFont = nil;
CGFloat buttonLabelMaxWidth = [self buttonLabelMaxWidth];
do {
if (fontSize < minimumFontSize) {
// Handle exception where the title just won't fit
break;
}
// Trying the current font size if it fits
labelFont = [UIFont systemFontOfSize:fontSize--];
CGSize boundingSize = [self boundingSizeForString:title font:labelFont];
labelHeightWithFont = boundingSize.height;
// Be sure that words are not truncated (that they fits in the maximum width)
longestWordWidth = 0;
for (NSString *word in [title componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
CGSize wordSize = [word sizeWithAttributes:#{NSFontAttributeName: labelFont}];
longestWordWidth = MAX(longestWordWidth, wordSize.width);
}
// Loop until the text at the current size fits the maximum width/height.
} while (labelHeightWithFont > buttonLabelMaxWidth || longestWordWidth > buttonLabelMaxWidth);
self.buttonLabel.text = title;
self.buttonLabel.font = labelFont;
}
- (CGSize)boundingSizeForString:(NSString *)string font:(UIFont *)font
{
CGRect boundingRect = [string boundingRectWithSize:CGSizeMake([self buttonLabelMaxWidth], MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:#{NSFontAttributeName: font}
context:nil];
return CGSizeMake(ceilf(boundingRect.size.width), ceilf(boundingRect.size.height));
}
I am using webservice to display content in my ios application.
On web the data is stored using rich edit text box.
so in response I am getting HTML text to display.
Now I had used UITextView to display html text in my application.
[_txt_desc setValue:#"html text here" forKey:#"contentToHTMLString"];
[_txt_desc setFont:[UIFont fontWithName:custom_font_name size:is_ipad?18:14]];
_txt_desc.editable = NO;
_txt_desc.scrollEnabled = NO;
Now I need to calculate size of the uitextview so I can reposition the views below the UItextview.
For calculating height of the uitextview I had used function described below.
+ (CGFloat)measureHeightOfUITextView:(UITextView *)textView withHtmlString:(NSString *)htmlString
{
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1)
{
// This is the code for iOS 7. contentSize no longer returns the correct value, so
// we have to calculate it.
//
// This is partly borrowed from HPGrowingTextView, but I've replaced the
// magic fudge factors with the calculated values (having worked out where
// they came from)
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
CGFloat measuredHeight=0;
if ([htmlString length]>0) {
textView.translatesAutoresizingMaskIntoConstraints = NO;
// NSAttributedString *attributedString = [[NSAttributedString alloc] initWithData: [htmlString dataUsingEncoding:NSUnicodeStringEncoding] options:#{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType ,NSParagraphStyleAttributeName : paragraphStyle,NSFontAttributeName:textView.font} documentAttributes:nil error:nil];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUnicodeStringEncoding] options:#{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType ,NSFontAttributeName:[UIFont fontWithName:custom_font_name size:is_ipad?16:14]} documentAttributes:nil error:nil];
textView.attributedText = attributedString;
// Take account of the padding added around the text.
}
CGRect frame = textView.bounds;
NSLog(#"content size %#",NSStringFromCGSize(textView.frame.size));
UIEdgeInsets textContainerInsets = textView.textContainerInset;
UIEdgeInsets contentInsets = textView.contentInset;
CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + textView.textContainer.lineFragmentPadding * 2 + contentInsets.left + contentInsets.right;
CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;
frame.size.width -= leftRightPadding;
frame.size.height -= topBottomPadding;
NSString *textToMeasure = textView.text;
if ([textToMeasure hasSuffix:#"\n"])
{
textToMeasure = [NSString stringWithFormat:#"%#-", textView.text];
}
// NSString class method: boundingRectWithSize:options:attributes:context is
// available only on ios7.0 sdk.
NSDictionary *attributes = #{ NSFontAttributeName: textView.font, NSParagraphStyleAttributeName : paragraphStyle,NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType };
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
return measuredHeight;
}
else
{
CGSize constraintSize = CGSizeMake(textView.frame.size.width, MAXFLOAT);
CGSize labelSize = [textView.text sizeWithFont:textView.font constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
int occurance = 0;
occurance=(int)[[htmlString componentsSeparatedByString:#"<p>"] count];
float height=0;
if(occurance>1){
height =occurance*(is_ipad?20:10);
}
occurance = (int)[[htmlString componentsSeparatedByString:#"<br/>"] count];
if(occurance>1){
height +=occurance*(is_ipad?20:10);
}
occurance = (int)[[htmlString componentsSeparatedByString:#"<br>"] count];
if(occurance>1){
height +=occurance*(is_ipad?20:10);
}
occurance = (int)[[htmlString componentsSeparatedByString:#"<h2>"] count];
if(occurance>1){
height +=occurance*(is_ipad?30:20);
}
occurance = (int)[[htmlString componentsSeparatedByString:#"<h3>"] count];
if(occurance>1){
height +=occurance*(is_ipad?30:20);
}
return labelSize.height+(is_ipad?30:20)+textView.font.pointSize+height;
}
}
Now it is returning the height properly.
But now issue is when it is displaying text in below version of ios 7.0 its text size is according to given programmatically while in ios 7 its not displaying text as given font size.
So what can be the solution for fontsize to display in ios 7.
Thanks in advance
bskania
Its simple. I hope this will help some one who is struggling with the Uitextview dynamic height issue.
[_txt_desc setValue:#"html text here" forKey:#"contentToHTMLString"];
[_txt_desc sizeThatFits:CGSizeMake(_txt_desc.frame.size.width,FLT_MAX)].height;
thanks
bskania.
Here's my goal:
I want to use a UITextView rather than a UILabel because I want users to be able to select text and copy.
I want the UITextView to max out at a height of 60 points.
I want the UITextView to have a fixed width of 300 points.
I want the UITextView to line break on words.
Let's say, based on the attributed text string I feed it, that it takes 3 lines to reach the 60 point max height. Therefore, if I feed the UITextView 6 lines worth of attributed text I want the UITextView to max out at 60 points and display 3 lines followed by an ellipsis (e.g. ...).
I don't want the text view to ever be scrollable.
If I feed the UITextView a single word as attributed text, such as "Hello", I want the UITextView to still have a fixed width of 300 points but a dynamic height that scales to as small as it can be, approximately 20 points for a single line of text in this example.
I want the UITextView to have zero internal padding.
Any ideas?
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andWidth:(CGFloat)width
{
UITextView *calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:text];
CGSize size = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}
- (void)viewDidLoad
{
// Invoke super
[super viewDidLoad];
// Get text of unknown length
NSMutableAttributedString *myAttributedString = [[NSMutableAttributedString alloc] initWithString:#"String of unknown length here..." attributes:#{NSForegroundColorAttributeName : [UIColor redColor]}];
// Get ellipsis w/ matching attributes
NSDictionary *endCharAttributes = [myAttributedString attributesAtIndex:myAttributedString.length - 1 effectiveRange:NULL];
NSAttributedString *ellipsis = [[NSAttributedString alloc] initWithString:#"..." attributes:endCharAttributes];
// Define size constraints
CGFloat maxHeight = 60;
CGFloat fixedWidth = 300;
// Get starting height
CGFloat textViewHeight = [self textViewHeightForAttributedText:myAttributedString andWidth:fixedWidth];
// Reduce string size and add ellipsis until we fit within our height constraint
while (textViewHeight > maxHeight)
{
NSLog(#"%f", textViewHeight);
NSRange substringRange = {0, myAttributedString.length - 6}; // Reducing by 6 works for my app (strings are never huge)
myAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[myAttributedString attributedSubstringFromRange:substringRange]];
[myAttributedString appendAttributedString:ellipsis];
NSLog(#"myAttributedString = %#", myAttributedString);
textViewHeight = [self textViewHeightForAttributedText:myAttributedString andWidth:fixedWidth];
}
// Init and config UITextView
UITextView *textView = [[UITextView alloc] init];
textView.attributedText = myAttributedString;
textView.frame = CGRectMake(0, 0, fixedWidth, textViewHeight);
[self.view addSubview:textView];
}
Have a more elegant solution? Post it!
UPDATE: You can increase the performance of - (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andWidth:(CGFloat)width by adding a helpers class and implementing the the following class methods:
// Private, gets us to alloc init calculation view one time for life of application
+ (UITextView *)calculationView
{
static UITextView *_calculationView;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_calculationView = [[UITextView alloc] init];
});
return _calculationView;
}
// Public, app calls this a lot
+ (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andWidth:(CGFloat)width usingUIEdgeInset:(UIEdgeInsets)edgeInsets
{
[self calculationView].textContainerInset = edgeInsets;
[self calculationView].attributedText = text;
CGSize size = [[self calculationView] sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}
Have you considered subclassing UILabel and adding the capability to select and copy text? Mattt Thompson has a good example here.
My solution is
- (NSString*)stringByTruncatingToWidth:(CGFloat)width maxHeight:(CGFloat)maxHeight font:(UIFont *)font;
{
NSString *ellipsis = #"…";
NSMutableString *truncatedString = [self mutableCopy];
if ([self textSizeForMaxWidth:width font:font].height > maxHeight)
{
truncatedString = [[truncatedString stringByAppendingString:ellipsis] mutableCopy];
NSRange range = {truncatedString.length - 4, 1};
[truncatedString deleteCharactersInRange:range];
while ([truncatedString textSizeForMaxWidth:width font:font].height > maxHeight){
[truncatedString deleteCharactersInRange:range];
range.location--;
}
}
return truncatedString;
}
And help method for calculating text size for max width
- (CGSize)textSizeForMaxWidth:(CGFloat)width font:(UIFont *)font
{
NSTextStorage *textStorage = [[NSTextStorage alloc]
initWithString:self];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize: CGSizeMake(width, MAXFLOAT)];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
[textStorage addAttribute:NSFontAttributeName value:font
range:NSMakeRange(0, [textStorage length])];
[textContainer setLineFragmentPadding:0.0];
[layoutManager glyphRangeForTextContainer:textContainer];
CGRect frame = [layoutManager usedRectForTextContainer:textContainer];
return frame.size;
}