Related
I want to resize the text size of UILabel dynamically. Frame of the UILabel will be changed dynamically. Whenever am changing the UILabel's Frame, font size should be fit with frame.
I have tried below code but there is no effect.
label.AdjustsFontSizeToFitWidth = true
Finally i found solution.
Here is my calculation
void ResizeLabelFontSize(CGRect frame,UILabel label)
{
int fontSize = 3;
UIFont font = UIFont.SystemFontOfSize(fontSize);
CGSize size = label.Text.StringSize(font);
while (frame.Width > size.Width)
{
fontSize++;
font = UIFont.SystemFontOfSize(fontSize);
size = label.Text.StringSize(font);
}
label.Font = UIFont.SystemFontOfSize(fontSize);
}
You can see AdjustsFontSizeToFitWidth definition here
As the documentation said, the size will be reduced in roder to fit the label's bounding rectangle, so the font will not get larger dynamically with label's frame, but you can set font very large at the beginning to achieve your requirement, see my test result .
Code snippet:
UILabel label;
public override void ViewDidLoad()
{
base.ViewDidLoad();
label = new UILabel(frame: new CGRect(0, 100, 100, 100));
label.BackgroundColor = UIColor.Red;
label.TextColor = UIColor.White;
label.Font = UIFont.SystemFontOfSize(1000);
label.AdjustsFontSizeToFitWidth = true;
label.LineBreakMode = UILineBreakMode.Clip;
label.BaselineAdjustment = UIBaselineAdjustment.AlignCenters;
label.Text = "label";
View.AddSubview(label);
}
partial void ButtonIncrease(UIButton sender)
{
CGRect rect = label.Frame;
rect.Width = 300;
rect.Height = 300;
label.Frame = rect;
}
partial void ButtonDecrease(UIButton sender)
{
CGRect rect = label.Frame;
rect.Width = 100;
rect.Height = 100;
label.Frame = rect;
}
I want to calculate the size of a string, to resize a UILabel. I used three different ways to calculate the size, to be exact, two ways. But I got three different results, and totally confused.
The most confusing thing is that I created a UILabel and use sizeToFit() to get the size. then I use the size to create a real UILabel and display it, but the label is truncated. Then I use sizeToFit() again to adjust the real UILabel, and the size is larger.
I doubted if it's caused by scale differences. But it is still three results on different simulators/devices.
Here's my code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let items = ["connected devices", "join session", "start host"]
let font = UIFont.systemFontOfSize(16)
var i = 0
for item in items {
let size = item.size(font: font)
print("\(size)")
let attriSize = item.sizeWithAttributes([NSFontAttributeName:font])
print("attriSize\(attriSize)")
let label = UILabel(frame: CGRect(origin: CGPoint(x: 50, y: 50 + i*50), size: size))
label.text = item
label.backgroundColor = UIColor.redColor()
label.sizeToFit()
print("\(label.bounds.size)")
view.addSubview(label)
++i
print("\n")
}
}
}
extension String {
func size(font font: UIFont) -> CGSize {
let label = UILabel(frame: CGRectZero)
label.text = self
label.font = font
label.sizeToFit()
let size = label.bounds.size
return size
}
}
and the results are
(137.333333333333, 19.3333333333333)
attriSize(137.0859375, 19.09375)
(144.666666666667, 20.3333333333333)
(84.3333333333333, 19.3333333333333)
attriSize(84.265625, 19.09375)
(89.0, 20.3333333333333)
(70.0, 19.3333333333333)
attriSize(69.765625, 19.09375)
(73.6666666666667, 20.3333333333333)
I really want to know what's wrong with sizeToFit(). Any help is appreciated.
Hi it is working example that can help you :
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
var newFrame = textView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
textView.frame = newFrame;
Currently in each of my views, I am setting the height of my segmented control in the viewDidLoad event like this:
mySegmentedControl.frame = CGRect(
x: mySegmentedControl.frame.origin.x,
y: mySegmentedControl.frame.origin.y,
width: mySegmentedControl.frame.size.width,
height: 22)
It works but it's tedious and was hoping I can do this in the AppDelegate.didFinishLaunchingWithOptions:
var frame = UISegmentedControl.appearance().frame
UISegmentedControl.appearance().frame = CGRect(
x: frame.origin.x,
y: frame.origin.y,
width: frame.size.width,
height: 22)
But this has no effect or even an error, it remains the default height. I even tried to subclass it like this but no effect or error either:
class SegmentedControl: UISegmentedControl {
override func sizeThatFits(size: CGSize) -> CGSize {
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = 22
return sizeThatFits
}
}
Is there something I'm missing or a way to set the height globally?
Maybe like that :
UIFont *font = [UIFont boldSystemFontOfSize:14.0f];
NSDictionary *attributes = [NSDictionary dictionaryWithObject:font
forKey:NSFontAttributeName];
[segmentedControl setTitleTextAttributes:attributes
forState:UIControlStateNormal];
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.
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());
}