How to calculate NSAttributedString text size - ios

I would like to calculate NSAttributedString text size.
But I couldn't.
```
func getTextSize(viewWidth:CGFloat, padding:CGFloat) -> CGSize {
var size:CGSize = CGSizeZero
if let s:CGSize = self.makeSize(viewWidth) {
size = CGSize(width: s.width, height: s.height + padding)
}
return size
}
// MARK: private
func makeSize(width:CGFloat) -> CGSize? {
var size:CGSize? = nil
if self.respondsToSelector("boundingRectWithSize:options:context:") {
let bounds:CGSize = CGSize(width: width, height: CGFloat.max)
let rect:CGRect = self.boundingRectWithSize(bounds, options:.UsesLineFragmentOrigin, context: nil)
size = CGSize(width: rect.size.width, height: rect.size.height)
}
return size
}
let no_multi_catchcopy = catchcopy!.stringByReplacingOccurrencesOfString(" ", withString: " ")
//行間
let attributedText = NSMutableAttributedString(string: no_multi_catchcopy)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 5
paragraphStyle.lineBreakMode = NSLineBreakMode.ByTruncatingTail
attributedText.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, attributedText.length))
let size = attributedText.getTextSize(self.view.bounds.width, padding: 10)
```
Line spacing was ignored by above way.
This returns same value.
【短期】★現金ポイントカードPRスタッフ募期】★現金ポイントカードPRスタッフ募\351\233集★(チラシ配布&ご案内)【東急沿線期】★現金ポイントカードPRスタッフ募\351\233集★(チラシ配布&ご案内)【東急沿線\343の駅構内】
(314.869921875, 28.8)
イベントスタッフ募集!一緒に盛り上げませんか??
(285.24, 28.8)
イベントスタッフ募集!一緒に盛り上げませんか??
(285.24, 28.8)
必見!!9/20 日給7時間で¥8,500!OMMで什器移動作業♪
(297.41015625, 28.8)
必見!!9/20 日給7時間で¥8,500!OMMで什器移見!!9/20 日給7時間で¥8,500!OMMで什器移\345\213動作業♪
(297.41015625, 28.8)
10/1~10/3(木金土)企業イベント運営
(213.6309375, 28.8)
10/1~10/3(木金土)企業イベント運営
(213.6309375, 28.8)
♪絵画催事の受付業務♪
(120.046875, 28.8)
UIFont is let font = UIFont(name: "HiraKakuProN-W6", size: 14)
By the way,the following code is another way.
+(CGFloat)heightForAttributedString:(NSAttributedString *)attrString forWidth:(CGFloat)inWidth
{
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
CGFloat width = inWidth;
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 < [attrString length]);
CFRelease(typesetter);
return ceil(y);
}
Font is japanese system font.
let font = UIFont(name: "HiraKakuProN-W6", size: 14){
But It can return correct height until 2 lines.
If it has 3 lines,method return same value as 2 line.
What is this problem?
Hirakaku is problem?

If You've fix width then you can try this:
NSAttributedString *attributedStr = ... // your attributed string
CGFloat width = 300; // whatever your desired width is
CGRect rect = [attributedStr boundingRectWithSize:CGSizeMake(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];
Without the proper values for the options parameter, you'll get wrong height.
Also, Without a font, there is no way to properly calculate the size.

Related

how to find actual height of auto growable UILabel

I have given number of lines to zero for UIlabel so that its height will adjust according its content.
sometimes If I give a big text it grows unto 4 lines in UI but when I access its
frame.size.height
it returns 21 instead of actual height which is way more bigger than that.
Any ideas on how to fix this ?
swift
public static func requiredHeightForLabel(text : String, font : UIFont, width : CGFloat) -> CGFloat {
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude))//UILabel(frame: CGRectMake(0, 0, width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
objective c
+(CGFloat)getLabelHeightForString:(NSString *)string font:(UIFont *)font
{
CGSize size = CGSizeMake(lablwidth, MAXFLOAT);
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGSize boundingBox = [string boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:context].size;
size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
return size.width;
}
In your View Controller you can use method which notify you about layout finishing:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("frame = \(label.frame)")
}

How to get CGFloat/CGSize based on String length?

I have a UIView, which acts as a container and the width of this container needs to change dynamically based on the longest String. So I want to get CGFloat/CGSize based on String length, is it still possible ?
The method which seems most helpful is now depreciated...
myStringSize : CGSize = [longestString sizeWithFont:myFont
constrainedToSize:maximumSize
lineBreakMode:self.myLabel.lineBreakMode];
Rather than basing it on a label I would like to base it on a String
You have to give Known Width or Height:
extension String {
func getHeight(with width: CGFloat, font: UIFont) -> CGFloat {
let maxSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let actualSize = self.boundingRect(with: maxSize, options: [.usesLineFragmentOrigin], attributes: [NSFontAttributeName: font], context: nil)
return actualSize.height
}
func getWidth(with height: CGFloat, font: UIFont) -> CGFloat {
let maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: height)
let actualSize = self.boundingRect(with: maxSize, options: [.usesLineFragmentOrigin], attributes: [NSFontAttributeName: font], context: nil)
return actualSize.width
}
}
1 - First OF all define The Max Size That you want MaxWidth
CGSize constraint = CGSizeMake(MaxWidth, CGFLOAT_MAX);
2- attributes for Your text :
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: [UIFont systemFontOfSize:FONT_SIZE], NSFontAttributeName, nil];
3-* This is How to get The paraRect From text:
CGRect paraRect = [text boundingRectWithSize:constraint options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes:attributes context:nil];
4-* Finally Get The Size :
CGSize size = paraRect.size;
Thank you for your help #Venkat, this is the solution that worked for me
#objc func containerWidth() -> (CGFloat){
let containerSize = self.myLabel.intrinsicContentSize
containerWidth = containerSize.width
return containerWidth
}

adjustsFontSizeToFitWidth making text too small/ trying to check font size

I am creating multiple buttons in my code, each one grabbing its text from separate arrays. I AM using the adjustsFontSizeToFitWidth = true (That was the only thing I could find that would automatically resize the text, so it fits within the frame of the button). However, when some the text adjusts, it can get ridiculously small; it's definitely smaller than it needs to be. Is there any way you can make sure the text only sizes as much as it needs to?
override func viewDidLoad() {
super.viewDidLoad()
correctWord.text = correctAnswer[questionNumber]
correctWord.frame = CGRectMake(0, 0, 1 * self.view.frame.width / 4, 1.5 * self.view.frame.height / 22)
correctWord.center = CGPointMake(self.view.bounds.width / 2, 14.25 * self.view.bounds.height / 22)
correctWord.textAlignment = NSTextAlignment.Center
correctWord.numberOfLines = 1
correctWord.font = UIFont(name: "Chalkboard SE", size: 60)
correctWord.textColor = UIColor.greenColor()
correctWord.adjustsFontSizeToFitWidth = true
correctWord.baselineAdjustment = .AlignCenters
correctWord.minimumScaleFactor = 0.1
self.view.addSubview(correctWord)
I DID try a code that I saw while searching (found below). I created a function for the code to update the text size, and I called it before self.view.addSubview(correctWord). But, the button never appears. And, is there a way you can check to see what the font size actually is? When I print currentSize to the logs to determine the font size, it always says "0". I am trying to determine which text's size is the smallest, so all of the buttons can be set to the same font size.
func adjustFontSizeToFitRect(rect : CGRect){
if correctWord.text == nil{
return
}
correctWord.frame = rect
let maxFontSize: CGFloat = 100.0
let minFontSize: CGFloat = 5.0
var q = Int(maxFontSize)
var p = Int(minFontSize)
let constraintSize = CGSize(width: rect.width, height: CGFloat.max)
while(p <= q){
currentSize = (p + q) / 2
correctWord.font = UIFont(name: "Chalkboard SE", size: CGFloat(currentSize))
let textRect = correctWord.text!.boundingRectWithSize(constraintSize, options: .UsesLineFragmentOrigin, attributes: nil, context: nil)
let labelSize = textRect.size
if labelSize.height < correctWord.frame.height && labelSize.height >= correctWord.frame.height-10 && labelSize.width < correctWord.frame.width && labelSize.width >= correctWord.frame.width-10 {
break
}else if labelSize.height > correctWord.frame.height || labelSize.width > correctWord.frame.width{
q = currentSize - 1
}else{
p = currentSize + 1
}
}
}

How to find actual number of lines of UILabel?

How can I find the actual number of lines of a UILabel after I have initialized it with a text and a font? I have set its numberOfLines property to 0, so it will expand to however many lines are necessary. But then, how can I find out how many lines it finally got after I set its text?
I found similar questions, but none seems to provide a concise answer and it seems to me that it must be really easy to get it without any overhead on juggling around with the boundingRectWithSize or sizeWithFont,...
None of these worked for me. Below one did,
Swift 4.2:
extension UILabel {
func calculateMaxLines() -> Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
let charSize = font.lineHeight
let text = (self.text ?? "") as NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
let linesRoundedUp = Int(ceil(textSize.height/charSize))
return linesRoundedUp
}
}
Swift 4/4.1:
extension UILabel {
func calculateMaxLines() -> Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
let charSize = font.lineHeight
let text = (self.text ?? "") as NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
let linesRoundedUp = Int(ceil(textSize.height/charSize))
return linesRoundedUp
}
}
Swift 3:
extension UILabel {
func calculateMaxLines() -> Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
let charSize = font.lineHeight
let text = (self.text ?? "") as NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
let linesRoundedUp = Int(ceil(textSize.height/charSize))
return linesRoundedUp
}
}
Swift 5 (IOS 12.2)
Get max number of lines required for a label to render the text without truncation.
extension UILabel {
var maxNumberOfLines: Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(MAXFLOAT))
let text = (self.text ?? "") as NSString
let textHeight = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil).height
let lineHeight = font.lineHeight
return Int(ceil(textHeight / lineHeight))
}
}
Get max number of lines can be displayed in a label with constrained bounds. Use this property after assigning text to label.
extension UILabel {
var numberOfVisibleLines: Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(MAXFLOAT))
let textHeight = sizeThatFits(maxSize).height
let lineHeight = font.lineHeight
return Int(ceil(textHeight / lineHeight))
}
}
Usage
print(yourLabel.maxNumberOfLines)
print(yourLabel.numberOfVisibleLines)
Firstly set text in UILabel
First Option :
Firstly calculate height for text according to font :
NSInteger lineCount = 0;
CGSize labelSize = (CGSize){yourLabel.frame.size.width, MAXFLOAT};
CGRect requiredSize = [self boundingRectWithSize:labelSize options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName: yourLabel.font} context:nil];
Now calculate number of lines :
int charSize = lroundf(yourLabel.font.lineHeight);
int rHeight = lroundf(requiredSize.height);
lineCount = rHeight/charSize;
NSLog(#"No of lines: %i",lineCount);
Second Option :
NSInteger lineCount = 0;
CGSize textSize = CGSizeMake(yourLabel.frame.size.width, MAXFLOAT);
int rHeight = lroundf([yourLabel sizeThatFits:textSize].height);
int charSize = lroundf(yourLabel.font.lineHeight);
lineCount = rHeight/charSize;
NSLog(#"No of lines: %i",lineCount);
Here is a swift version of #Paresh solution:
func lines(label: UILabel) -> Int {
let textSize = CGSize(width: label.frame.size.width, height: CGFloat(Float.infinity))
let rHeight = lroundf(Float(label.sizeThatFits(textSize).height))
let charSize = lroundf(Float(label.font.lineHeight))
let lineCount = rHeight/charSize
return lineCount
}
EDIT: I don't know why, but the code is returning 2 more lines than the actual number of lines, for my solution, I just subtracted them before returning lineCount.
Swift 5.2
The main point to make it work for me was to call label.layoutIfNeeded() because I was using autoLayout, otherwise it doesnt work.
func actualNumberOfLines(label: UILabel) -> Int {
// You have to call layoutIfNeeded() if you are using autoLayout
label.layoutIfNeeded()
let myText = label.text! as NSString
let rect = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: label.font as Any], context: nil)
return Int(ceil(CGFloat(labelSize.height) / label.font.lineHeight))
}
Credits to: https://gist.github.com/fuxingloh/ccf26bb68f4b8e6cfd02, which provided the solution in an older swift version, and for mentioning the importance of layoutIfNeeded().
The other answers here don't respect the numberOfLines property of UILabel when it is set to something other than 0.
Here's another option you can add to your category or subclass:
- (NSUInteger)lineCount
{
CGSize size = [self sizeThatFits:CGSizeMake(self.frame.size.width, CGFLOAT_MAX)];
return MAX((int)(size.height / self.font.lineHeight), 0);
}
Some notes:
I'm using this on a UILabel with attributed text, without ever actually setting the font property, and it's working fine. Obviously you would run into issues if you were using multiple fonts in your attributedText.
If you are subclassing UILabel to have custom edge insets (for example by overriding drawTextInRect:, which is a neat trick I found here), then you must remember to take those insets into account when calculating the size above. For example: CGSizeMake(self.frame.size.width - (self.insets.left + self.insets.right), CGFLOAT_MAX)
Here is the Swift3 Code
here you can define Int value and get the height of text size by using (MAXFLOAT) and using that height you can get the total height of UILabel and by deviding that total height by character size you can get the actual line count of UILabel.
var lineCount: Int = 0
var textSize = CGSize(width: CGFloat(yourLabel.frame.size.width), height: CGFloat(MAXFLOAT))
var rHeight: Int = lroundf(yourLabel.sizeThatFits(textSize).height)
var charSize: Int = lroundf(yourLabel.font.leading)
lineCount = rHeight / charSize
print("No of lines: \(lineCount)")
It seems that the official developer website mentions one solution Counting Lines of Text in Objc. However, it assumes you have a reference to a text view configured with a layout manager, text storage, and text container. Unfortunately, UILabel doesn't expose those to us, so we need create them with the same configuration as the UILabel.
I translated the Objc code to swift as following. It seems work well for me.
extension UILabel {
var actualNumberOfLines: Int {
let textStorage = NSTextStorage(attributedString: self.attributedText!)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: self.bounds.size)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = self.lineBreakMode
layoutManager.addTextContainer(textContainer)
let numberOfGlyphs = layoutManager.numberOfGlyphs
var numberOfLines = 0, index = 0, lineRange = NSMakeRange(0, 1)
while index < numberOfGlyphs {
layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
index = NSMaxRange(lineRange)
numberOfLines += 1
}
return numberOfLines
}
}
You can find the total number of line available in your custom label
Please check this code...
NSInteger numberOfLines = [self lineCountForText:#"YOUR TEXT"];
- (int)lineCountForText:(NSString *) text
{
UIFont *font = [UIFont systemFontOfSize: 15.0];
int width=Your_LabelWidht;
CGRect rect = [text boundingRectWithSize:CGSizeMake(width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : font} context:nil];
return ceil(rect.size.height / font.lineHeight);
}
Following up on #Prince's answer, I now implemented a category on UILabel as follows (note that I corrected some minor syntax mistakes in his answer that wouldn't let the code compile):
UILabel+Util.h
#import <UIKit/UIKit.h>
#interface UILabel (Util)
- (NSInteger)lineCount;
#end
UILabel+Util.,
#import "UILabel+Util.h"
#implementation UILabel (Util)
- (NSInteger)lineCount
{
// Calculate height text according to font
NSInteger lineCount = 0;
CGSize labelSize = (CGSize){self.frame.size.width, FLT_MAX};
CGRect requiredSize = [self.text boundingRectWithSize:labelSize options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName: self.font} context:nil];
// Calculate number of lines
int charSize = self.font.leading;
int rHeight = requiredSize.size.height;
lineCount = rHeight/charSize;
return lineCount;
}
#end
Xamarin iOS
label.Text = text;
var lineCount = 0;
var textSize = new CGSize(label.Frame.Size.Width, float.MaxValue);
var height = label.SizeThatFits(textSize).Height;
var fontHeight = label.Font.LineHeight;
lineCount = Convert.ToInt32(height / fontHeight);
Note that #kurt-j's answer will not always work. In some cases, you will have to manually provide the width of label. Since these cases exist, it is a good idea to have an optional width parameter, even if you don't end up using it.
Swift 4.2:
extension UILabel {
func calculateMaxLines(actualWidth: CGFloat?) -> Int {
var width = frame.size.width
if let actualWidth = actualWidth {
width = actualWidth
}
let maxSize = CGSize(width: width, height: CGFloat(Float.infinity))
let charSize = font.lineHeight
let text = (self.text ?? "") as NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
let linesRoundedUp = Int(ceil(textSize.height/charSize))
return linesRoundedUp
}
}
let l = UILabel()
l.numberOfLines = 0
l.layer.frame.size.width = self.view.frame.width - 40 /*padding(20 + 20)*/
l.font = UIFont(name: "BwModelica-Bold", size: 16.0)
l.text = "Random Any length Text!!"
let noOfLines = ceil(l.intrinsicContentSize.width / l.frame.size.width)
let lbl_height = noOfLines * l.intrinsicContentSize.height
This will be your Exact dynamic height of Label and Number of lines. Happy coding!!!
Xamarin.iOS
Thanks to the answers everyone provided above.
This gets number of visible lines.
public static int VisibleLineCount(this UILabel label)
{
var textSize = new CGSize(label.Frame.Size.Width, nfloat.MaxValue);
nfloat rHeight = label.SizeThatFits(textSize).Height;
nfloat charSize = label.Font.LineHeight;
return Convert.ToInt32(rHeight / charSize);
}
This gets actual number of lines the text will occupy on screen.
public static int LineCount(this UILabel label)
{
var maxSize = new CGSize(label.Frame.Size.Width, nfloat.MaxValue);
var charSize = label.Font.LineHeight;
var text = (label.Text ?? "").ToNSString();
var textSize = text.GetBoundingRect(maxSize, NSStringDrawingOptions.UsesLineFragmentOrigin, new UIStringAttributes() { Font = label.Font }, null);
return Convert.ToInt32(textSize.Height / charSize);
}
A helper method I find useful for my use case.
public static bool IsTextTruncated(this UILabel label)
{
if (label.Lines == 0)
{
return false;
}
return (label.LineCount() > label.Lines);
}
To get a more accurate line count:
Use font.lineHeight instead of font.pointSize
round() the line count after division
Swift 5.4
Refactor solution of Fernando Cardenas to UILabel extension
private extension UILabel {
var actualNumberOfLines: Int {
guard let text = self.text else {
return 0
}
layoutIfNeeded()
let rect = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
let labelSize = text.boundingRect(
with: rect,
options: .usesLineFragmentOrigin,
attributes: [NSAttributedString.Key.font: font as Any],
context: nil)
return Int(ceil(CGFloat(labelSize.height) / font.lineHeight))
}
}
⚠️ Nor lineHeight, nor leading is sufficient on its own.
The font.lineHeight is the height of the glyphs, it spans from descender (the bottom of the lowest glyph) to ascender (the top of the highest glyph). Also, note that lineHeight can be override for an attributed string. The font.leading is the additional (may be negative, though) space between (!) the lines (see docs for yourself). If you use the system fonts, you get different leading values for almost every point size.
So e.g. the height of a label with 5 lines is consist of 5 lineHeight and 4 leading (yet the leading value is often small enough to make the above solutions work up until a point where you start to work with a multitude of lines.
So the correct (pseudo) formula should be:
(frame.size.height + font.leading) / (font.lineHeight + font.leading)
Also also, if the attributed string has an attachment that is too big (higher than the ascender of the font), then it also alters the line height for that row.

How to check if UILabel is truncated?

I have a UILabel that can be varying lengths depending on whether or not my app is running in portrait or landscape mode on an iPhone or iPad. When the text is too long to show on one line and it truncates I want the user to be able to press it and get a popup of the full text.
How can I check to see if the UILabel is truncating the text? Is it even possible? Right now I'm just checking for different lengths based on what mode I'm in but it does not work super well.
You can calculate the width of the string and see if the width is greater than label.bounds.size.width
NSString UIKit Additions has several methods for computing the size of the string with a specific font. However, if you have a minimumFontSize for your label that allows the system to shrink the text down to that size. You may want to use sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode: in that case.
CGSize size = [label.text sizeWithAttributes:#{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
...
}
Swift (as extension) - works for multi line uilabel:
swift4: (attributes param of boundingRect changed slightly)
extension UILabel {
var isTruncated: Bool {
guard let labelText = text else {
return false
}
let labelTextSize = (labelText as NSString).boundingRect(
with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: font],
context: nil).size
return labelTextSize.height > bounds.size.height
}
}
swift3:
extension UILabel {
var isTruncated: Bool {
guard let labelText = text else {
return false
}
let labelTextSize = (labelText as NSString).boundingRect(
with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
return labelTextSize.height > bounds.size.height
}
}
swift2:
extension UILabel {
func isTruncated() -> Bool {
if let string = self.text {
let size: CGSize = (string as NSString).boundingRectWithSize(
CGSize(width: self.frame.size.width, height: CGFloat(FLT_MAX)),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: self.font],
context: nil).size
if (size.height > self.bounds.size.height) {
return true
}
}
return false
}
}
EDIT: I just saw my answer was upvoted, but the code snippet I gave is deprecated.
Now the best way to do this is (ARC) :
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = mylabel.lineBreakMode;
NSDictionary *attributes = #{NSFontAttributeName : mylabel.font,
NSParagraphStyleAttributeName : paragraph};
CGSize constrainedSize = CGSizeMake(mylabel.bounds.size.width, NSIntegerMax);
CGRect rect = [mylabel.text boundingRectWithSize:constrainedSize
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:attributes context:nil];
if (rect.size.height > mylabel.bounds.size.height) {
NSLog(#"TOO MUCH");
}
Note the calculated size is not integer value. So if you do things like int height = rect.size.height, you will lose some floating point precision and may have wrong results.
Old answer (deprecated) :
If your label is multiline, you can use this code :
CGSize perfectSize = [mylabel.text sizeWithFont:mylabel.font constrainedToSize:CGSizeMake(mylabel.bounds.size.width, NSIntegerMax) lineBreakMode:mylabel.lineBreakMode];
if (perfectSize.height > mylabel.bounds.size.height) {
NSLog(#"TOO MUCH");
}
Swift 3
You can count the number of lines after assigning the string and compare to the max number of lines of the label.
import Foundation
import UIKit
extension UILabel {
func countLabelLines() -> Int {
// Call self.layoutIfNeeded() if your view is uses auto layout
let myText = self.text! as NSString
let attributes = [NSFontAttributeName : self.font]
let labelSize = myText.boundingRect(with: CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil)
return Int(ceil(CGFloat(labelSize.height) / self.font.lineHeight))
}
func isTruncated() -> Bool {
guard numberOfLines > 0 else { return false }
return countLabelLines() > numberOfLines
}
}
you can make a category with UILabel
- (BOOL)isTextTruncated
{
CGRect testBounds = self.bounds;
testBounds.size.height = NSIntegerMax;
CGRect limitActual = [self textRectForBounds:[self bounds] limitedToNumberOfLines:self.numberOfLines];
CGRect limitTest = [self textRectForBounds:testBounds limitedToNumberOfLines:self.numberOfLines + 1];
return limitTest.size.height>limitActual.size.height;
}
It seems intrinsicContentSize will get the job done for labels with text set with attributedText and text. With that in mind, I think we can safely ditch all the bounding box bookkeeping and simplify as follows:
Swift 5.x
extension UILabel {
var isTruncated: Bool {
frame.width < intrinsicContentSize.width
}
var isClipped: Bool {
frame.height < intrinsicContentSize.height
}
}
To add to iDev 's answer, you should use intrinsicContentSize instead of frame, to make it works for Autolayout
- (BOOL)isTruncated:(UILabel *)label{
CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.intrinsicContentSize.width, CGFLOAT_MAX)
options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;
if (self.intrinsicContentSize.height < ceilf(sizeOfText.height)) {
return YES;
}
return NO;
}
Use this category to find if a label is truncated on iOS 7 and above.
// UILabel+Truncation.h
#interface UILabel (Truncation)
#property (nonatomic, readonly) BOOL isTruncated;
#end
// UILabel+Truncation.m
#implementation UILabel (Truncation)
- (BOOL)isTruncated
{
CGSize sizeOfText =
[self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{ NSFontAttributeName : label.font }
context: nil].size;
if (self.frame.size.height < ceilf(sizeOfText.height))
{
return YES;
}
return NO;
}
#end
This is it. This works with attributedText, before falling back to plain text, which makes a lot of sense for us folks who deal with multiple font families, sizes, and even NSTextAttachments!
Works fine with autolayout, but obviously the constraints must be defined and set before we check isTruncated, otherwise the label itself wont even know how to lay itself out, so no way it would even know if its truncated.
It doesnt work to approach this problem with just a plain NSString and sizeThatFits. Im not sure how people were getting positive results like that. BTW, as mentioned numerous times, using sizeThatFits is not ideal at all because it takes into account numberOfLines for the resulting size, which defeats the whole purpose of what we are trying to do, because isTruncated would always return false regardless if its truncated or not.
extension UILabel {
var isTruncated: Bool {
layoutIfNeeded()
let rectBounds = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
var fullTextHeight: CGFloat?
if attributedText != nil {
fullTextHeight = attributedText?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, context: nil).size.height
} else {
fullTextHeight = text?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil).size.height
}
return (fullTextHeight ?? 0) > bounds.size.height
}
}
Here's the selected answer in Swift 3 (as an extension). The OP was asking about 1 line labels. Many of the swift answers I tried here are specific to multi-line labels and aren't flagging correctly on single line labels.
extension UILabel {
var isTruncated: Bool {
guard let labelText = text as? NSString else {
return false
}
let size = labelText.size(attributes: [NSFontAttributeName: font])
return size.width > self.bounds.width
}
}
This works for iOS 8:
CGSize size = [label.text boundingRectWithSize:CGSizeMake(label.bounds.size.width, NSIntegerMax) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : label.font} context:nil].size;
if (size.height > label.frame.size.height) {
NSLog(#"truncated");
}
I have written a category for working with UILabel's truncation. Works on iOS 7 and later. Hope it helps !
uilabel tail truncation
#implementation UILabel (Truncation)
- (NSRange)truncatedRange
{
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
textContainer.lineFragmentPadding = 0;
[layoutManager addTextContainer:textContainer];
NSRange truncatedrange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:0];
return truncatedrange;
}
- (BOOL)isTruncated
{
return [self truncatedRange].location != NSNotFound;
}
- (NSString *)truncatedText
{
NSRange truncatedrange = [self truncatedRange];
if (truncatedrange.location != NSNotFound)
{
return [self.text substringWithRange:truncatedrange];
}
return nil;
}
#end
extension UILabel {
public func resizeIfNeeded() -> CGFloat? {
guard let text = text, !text.isEmpty else { return nil }
if isTruncated() {
numberOfLines = 0
sizeToFit()
return frame.height
}
return nil
}
func isTruncated() -> Bool {
guard let text = text, !text.isEmpty else { return false }
let size: CGSize = text.size(withAttributes: [NSAttributedStringKey.font: font])
return size.width > self.bounds.size.width
}
}
You can calculate the width of the string and see if the width is greater than label width.
To add to what #iDev did, I modified the self.frame.size.height to use label.frame.size.height and also did not use NSStringDrawingUsesLineFontLeading. After those modifications, I achieved perfect calculation of when the truncation would happen (at least for my case).
- (BOOL)isTruncated:(UILabel *)label {
CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.bounds.size.width, CGFLOAT_MAX)
options: (NSStringDrawingUsesLineFragmentOrigin)
attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;
if (label.frame.size.height < ceilf(sizeOfText.height)) {
return YES;
}
return NO;
}
I had issues with boundingRect(with:options:attributes:context:) when using autolayout (to set a max height) and an attributed text with NSParagraph.lineSpacing
The spacing between lines was ignored (even when passed in attributes to the boundingRect method) so the label might be considered as not truncated when it was.
The solution I found is to use UIView.sizeThatFits :
extension UILabel {
var isTruncated: Bool {
layoutIfNeeded()
let heightThatFits = sizeThatFits(bounds.size).height
return heightThatFits > bounds.size.height
}
}
Make sure to call either of these in viewDidLayoutSubviews.
public extension UILabel {
var isTextTruncated: Bool {
layoutIfNeeded()
return text?.boundingRect(with: CGSize(width: bounds.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font!], context: nil).size.height ?? 0 > bounds.size.height
}
var isAttributedTextTruncated: Bool {
layoutIfNeeded()
return attributedText?.boundingRect(with: CGSize(width: bounds.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).size.height ?? 0 > bounds.size.height
}
}
SWIFT 5
Example for a multiple lined UILabel that is set to display only 3 lines.
let labelSize: CGSize = myLabel.text!.size(withAttributes: [.font: UIFont.systemFont(ofSize: 14, weight: .regular)])
if labelSize.width > myLabel.intrinsicContentSize.width * 3 {
// your label will truncate
}
Though the user may select the return key adding an extra line without adding to the "text width" in that case something like this may also be useful.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
// return pressed
}
}
In Swift 5.x
let size = label.text?.size(withAttributes: [NSAttributedString.Key.font: label.font!])
if size!.width > label.bounds.size.width {
debugPrint("Size increased", size?.width ?? 0, label.bounds.size.width, label.text ?? "")
}
Because all the answers above use depreciated methods, i thought this could be useful:
- (BOOL)isLabelTruncated:(UILabel *)label
{
BOOL isTruncated = NO;
CGRect labelSize = [label.text boundingRectWithSize:CGSizeFromString(label.text) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : label.font} context:nil];
if (labelSize.size.width / labelSize.size.height > label.numberOfLines) {
isTruncated = YES;
}
return isTruncated;
}
Using numberOfLines is not always useful because it may be zero and label can be truncated because of height constraints. Also, intrinsicContentSize didn't return the correct size in my case. You can use this extension:
extension UILabel {
var currentContentSize: CGSize {
layoutIfNeeded()
let myText = text! as NSString
let attributes: [NSAttributedString.Key: Any] = [.font: font!]
let labelRect = myText.boundingRect(with: CGSize(width: bounds.width,
height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: attributes,
context: nil)
return CGSize(width: labelRect.width, height: labelRect.height)
}
var isTruncated: Bool {
layoutIfNeeded()
return frame.width < currentContentSize.width || frame.height < currentContentSize.height
}
}
To handle iOS 6 (yes, some of us still have to), here's yet another expansion to #iDev's answer. The key takeaway is that, for iOS 6, to make sure your UILabel's numberOfLines is set to 0 before calling sizeThatFits; if not, it'll give you a result that says "the points to draw numberOfLines worth of height" is needed to draw the label text.
- (BOOL)isTruncated
{
CGSize sizeOfText;
// iOS 7 & 8
if([self.text respondsToSelector:#selector(boundingRectWithSize:options:attributes:context:)])
{
sizeOfText = [self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:self.font}
context:nil].size;
}
// iOS 6
else
{
// For iOS6, set numberOfLines to 0 (i.e. draw label text using as many lines as it takes)
// so that siteThatFits works correctly. If we leave it = 1 (for example), it'll come
// back telling us that we only need 1 line!
NSInteger origNumLines = self.numberOfLines;
self.numberOfLines = 0;
sizeOfText = [self sizeThatFits:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)];
self.numberOfLines = origNumLines;
}
return ((self.bounds.size.height < sizeOfText.height) ? YES : NO);
}
Swift 3 solution
I think the best solution is to (1) create a UILabel with the same properties as the label you're checking for truncation, (2) call .sizeToFit(), (3) compare the attributes of the dummy label with your actual label.
For example, if you want to check whether a one lined label that has varying width truncates or not, then you can use this extension:
extension UILabel {
func isTruncated() -> Bool {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: CGFloat.greatestFiniteMagnitude, height: self.bounds.height))
label.numberOfLines = 1
label.font = self.font
label.text = self.text
label.sizeToFit()
if label.frame.width > self.frame.width {
return true
} else {
return false
}
}
}
...but again, you can easily modify the above code to fit your needs. So let's say your label is multilined and has varying height. Then the extension would look something like this:
extension UILabel {
func isTruncated() -> Bool {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.font = self.font
label.text = self.text
label.sizeToFit()
if label.frame.height > self.frame.height {
return true
} else {
return false
}
}
}
Wouldnt it be easy to set the title attribute for the label , setting this will display full label when hovered.
you can calculate the length of the label and div width (convert to length - jQuery / Javascript - How do I convert a pixel value (20px) to a number value (20)).
set jquery to set title if length is greater than the div width.
var divlen = parseInt(jQuery("#yourdivid").width,10);
var lablen =jQuery("#yourlabelid").text().length;
if(lablen < divlen){
jQuery("#yourlabelid").attr("title",jQuery("#yourlabelid").text());
}

Resources