Wrong bounding rect for right aligned text in UILabel - ios

- (CGRect)boundingRectForGlyphRange:(NSRange)glyphRange inTextContainer:(NSTextContainer *)container;
returns wrong rect for glyph range when attributed text is right aligned in UILabel. How to fix this please ?
Code to calculate the bounding rect of attributed text when tapped on UILabel (textAlignment set to right in UILabel)
- (void)tap:(UITapGestureRecognizer *)recognizer {
UILabel *label = (UILabel *)recognizer.view;
CGSize labelSize = recognizer.view.bounds.size;
NSTextContainer *container = [[NSTextContainer alloc] initWithSize:labelSize];
container.lineFragmentPadding = 0.0;
container.lineBreakMode = label.lineBreakMode;
container.maximumNumberOfLines = label.numberOfLines;
NSLayoutManager *manager = [NSLayoutManager new];
[manager addTextContainer:container];
NSTextStorage *storage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
[storage addLayoutManager:manager];
CGPoint touchPoint = [recognizer locationInView:label];
NSInteger indexOfCharacter = [manager characterIndexForPoint:touchPoint
inTextContainer:container
fractionOfDistanceBetweenInsertionPoints:nil];
NSRange range = NSMakeRange(indexOfCharacter, 1);
NSRange glyphRange;
[manager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];
CGRect rect = [manager boundingRectForGlyphRange:glyphRange inTextContainer:container];
}

The problem is that you are setting the label's text and textAlignment, and then pulling out the label's attributedText and handing it to the text kit stack, in the belief that it magically translates your label configuration into an attributed string. It doesn't!
If you want right-aligned text that the text kit stack can see as right-aligned, set your label's attributedText only, endowing it with features such as a right-aligned paragraph style.

Solved this!!
The problem was the label was made to be right aligned using textAlignment property of UILabel. As mentioned in comments label.attributedText doesn't know anything about it.
Instead add text alignment using NSParagraphStyleAttributeName as :
NSMutableAttributedString *mutableString = [NSMutableAttributedString new];
//Add to your string
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setAlignment:NSTextAlignmentRight];
[mutableString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, mutableString.length)];
label.attributedText = mutableString;

Related

UITextView, NSAttributedString and custom attributes

I have searched a lot on Stack Overflow but I couldn't find a solution. Perhaps I just misinterpreted some answers.
I have created a UITextView and I am using NSAttributedStrings to work with the UITextView which is just fine.
Now, after adding a custom attribute, I am stuck.
Where can I hook in to render my custom attribute within the UITextView? Is there a delegate method, or will I have to create my own UITextView and overwrite a method?
You can custom NSLayoutManager, and implement it's -drawGlyphsForGlyphRange:atPoint: method.
For example, you want a custom background with a corner radius
textView init:
NSTextStorage *textStorage = [NSTextStorage new];
CustomLayoutManager *layoutManager = [[CustomLayoutManager alloc] init];
CGSize containerSize = CGSizeMake(self.view.bounds.size.width, CGFLOAT_MAX);
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:containerSize];
textContainer.widthTracksTextView = YES;
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
self.textView = [[UITextView alloc] initWithFrame:yourFrame textContainer:textContainer];
And apply your custom attribute:
NSMutableAttributedString *mAttrStr = [[NSMutableAttributedString alloc] initWithString:#"SampleText"];
[mAttrStr addAttribute:YourCustomAttributeName value:[UIColor redColor] range:NSMakeRange(0, mAttrStr.length)]; //for example, you want a custom background with a corner radius
[self.textView.textStorage appendAttributedString:mAttrStr];
In CustomLayoutManager.m
-(void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
NSRange range = [self characterRangeForGlyphRange:glyphsToShow
actualGlyphRange:NULL];
//enumerate custom attribute in the range
[self.textStorage enumerateAttribute:YourCustomAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
if (value) {
UIColor *color = value; //the color set above
NSRange glyphRange = [self glyphRangeForCharacterRange:range
actualCharacterRange:NULL];
NSTextContainer *container = [self textContainerForGlyphAtIndex:glyphRange.location
effectiveRange:NULL];
//draw background
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, origin.x, origin.y);
[color setFill];
CGRect rect = [self boundingRectForGlyphRange:glyphRange inTextContainer:container];
//UIBezierPath with rounded
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:100];
[path fill];
CGContextRestoreGState(context);
//end draw
[super drawGlyphsForGlyphRange:range atPoint:origin];
}
else {
[super drawGlyphsForGlyphRange:range atPoint:origin];
}
}];
}
Now the 'SampleText' has a red rounded background.
Please refer this simple code snippet to set attributed string to a textview
let attributedString = NSMutableAttributedString(string:"Test string to add attributes")
attributedString.addAttributes([NSForegroundColorAttributeName:UIColor.greenColor()], range: NSMakeRange(0, attributedString.string.characters.count))
textView.attributedText = attributedString
For Objective-C
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:#"Test string to add attributes"];
[attributedString addAttributes:#{NSForegroundColorAttributeName:[UIColor greenColor]} range:NSMakeRange(0, attributedString.string.length)];
textView.attributedText = attributedString;
Hope this helps.
If you want apply particular attributes for particular textView rather then string then you should subclass UITextView and make custom initmethod or some method that return UITextView object with specified attribute!! You can pass custom attributes as parameter in method also if attributes are change i mean not fix. and if attribute will remain same implicitly then set attributes in that class by default.

Exact height of NSTextContainer

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

UILabel - attributedText - setLineHeightMultiple

i´m using a UILabel to show a text.
The problem is, that i have different line spaces between different IOS versions. IOS7.1 is less space than IOS8.1
What the best way to fix it? I don´t have all simulators installed, so i can´t figure out, when the problem appears. Does anyone have a solution? This is the current code:
NSString *startGameBasic_title = #"TRACK QUIZ"; // FONT SIZE 20 px
NSString *startGameBasic_1 = #"BASIC"; // FONT SIZE 76 px
NSString *startGameBasic_2 = #"MODE"; // FONT SIZE 76 px
NSString *startGameBasic_textRe = [NSString stringWithFormat:#"%#\n\n%#\n%#",
startGameBasic_title,
startGameBasic_1,
startGameBasic_2];
// Define general attributes like color and fonts for the entire text
NSDictionary *startGameBasic_attribsRe = #{
NSForegroundColorAttributeName: GENERAL_BACK_BUTTON_COLOR,
NSFontAttributeName:START_VIEW_FONT_SMALL,
NSKernAttributeName:#(0.0f)
};
NSMutableAttributedString *startGameBasic_attributedTextRe = [[NSMutableAttributedString alloc] initWithString:startGameBasic_textRe attributes:startGameBasic_attribsRe];
//TITEL
NSRange startGameBasic_text_title = [startGameBasic_textRe rangeOfString:startGameBasic_title];
[startGameBasic_attributedTextRe setAttributes:#{NSForegroundColorAttributeName:GENERAL_BACK_BUTTON_COLOR,
NSFontAttributeName:START_VIEW_FONT_SMALL,
NSKernAttributeName:#(-0.5f)}
range:startGameBasic_text_title];
//FIRSTROW
NSRange startGameBasic_text_1 = [startGameBasic_textRe rangeOfString:startGameBasic_1];
[startGameBasic_attributedTextRe setAttributes:#{NSForegroundColorAttributeName:GENERAL_BACK_BUTTON_COLOR,
NSFontAttributeName:START_BASIC_BUTTON,
NSKernAttributeName:#(-3.5f)}
range:startGameBasic_text_1];
//SECONDROW
NSRange buttonReStart_text_2 = [startGameBasic_textRe rangeOfString:startGameBasic_2];
[startGameBasic_attributedTextRe setAttributes:#{NSForegroundColorAttributeName:GENERAL_BACK_BUTTON_COLOR,
NSFontAttributeName:START_BASIC_BUTTON,
NSKernAttributeName:#(-2.8f)}
range:buttonReStart_text_2];
// Create NSMutableParagraphStyle object
NSMutableParagraphStyle *startGameBasic_paragraphRe = [[NSMutableParagraphStyle alloc] init];
startGameBasic_paragraphRe.alignment = NSTextAlignmentCenter;
[startGameBasic_paragraphRe setLineHeightMultiple:0.65];
[startGameBasic_attributedTextRe addAttribute:NSParagraphStyleAttributeName value:startGameBasic_paragraphRe range:NSMakeRange(0, [startGameBasic_attributedTextRe length])];
startGameBasic_txt.attributedText = startGameBasic_attributedTextRe;
Right now, i have the idea to do it like this, but maybe there is much better way:
NSMutableParagraphStyle *startGameBasic_paragraphRe = [[NSMutableParagraphStyle alloc] init];
startGameBasic_paragraphRe.alignment = NSTextAlignmentCenter;
float SystemVersion = [[UIDevice currentDevice].systemVersion floatValue];
if (SystemVersion < 8.0)
{
[startGameBasic_paragraphRe setLineHeightMultiple:0.85];
} else {
[startGameBasic_paragraphRe setLineHeightMultiple:0.65];
}
I think to use the UITextView is not a solution, as i can´t set "scale font size". Would be great to get your help.

How to set max height and fixed width for UITextView and force ellipsis rather than scroll if overflow occurs?

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;
}

How to obtain adjusted font size from UILabel when adjustsFontSizeToFitWidth is YES in iOS 7?

I have a label that is set to adjustsFontSizeToFitWidth = YES and I need to get the actual displayed font size.
Now iOS 7 deprecated all methods that worked previously and all questions on SO suggest using these deprecated methods.
I will make this question a bounty as soon as I am allowed to by SO. Please do not close.
UILabel displayed fontSize in case of using adjustsFontSizeToFitWidth in iOS 7
UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 40)];
label.text = #" Your Text goes here into this label";
label.adjustsFontSizeToFitWidth = YES;
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:label.attributedText];
// Get the theoretical font-size
[attrStr setAttributes:#{NSFontAttributeName:label.font} range:NSMakeRange(0, attrStr.length)];
NSStringDrawingContext *context = [NSStringDrawingContext new];
context.minimumScaleFactor = label.minimumScaleFactor;
[attrStr boundingRectWithSize:label.frame.size options:NSStringDrawingUsesLineFragmentOrigin context:context];
CGFloat theoreticalFontSize = label.font.pointSize * context.actualScaleFactor;
NSLog(#"theoreticalFontSize: %f",theoreticalFontSize);
NSLog(#"AttributedString Width: %f", [attrStr size].width);
double scaleFactor=label.frame.size.width/([attrStr size].width);
double displayedFontSize=theoreticalFontSize*scaleFactor;
NSLog(#"Actual displayed Font Size: %f",displayedFontSize);
// Verification of Result
double verification=(displayedFontSize * [attrStr length]);
NSLog(#"Should be equal to %0.5f: %0.5f ", [attrStr size].width/17.0, label.frame.size.width/displayedFontSize);
Try inserting [lblObj sizeToFit] just before requesting the font size
There's a readonly property that lets you do that. You can access it like this
nameLabel.adjustsFontSizeToFitWidth = YES;
//Make sure to use the line below AFTER the line above
float fontSize = nameLabel.font.xHeight;
This will give you the font size after it has been adjusted to fit width.
You can get the font size of UILabel text using these line of code.
UILabel *lblObj = [[UILabel alloc]init];
lblObj.text = #" Your Text";
lblObj.adjustsFontSizeToFitWidth = YES;
float size = lblObj.font.pointSize; //Here You will get the actual size of the text.
float lineHeight = lblObj.font.lineHeight;
Try this one.

Resources