AutoLayout link two UILabels to have the same font size - ios

I have two UILabels next to each other in row with left and right adjustments so that it looks like below.
|-Some text left adjusted----------some other text right adjusted-|
Both labels have adjustsFontSizeToFitWidth = YES and are linked to each other with the following constraint
[NSLayoutConstraint constraintWithItem:_rightLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:_leftLabel
attribute:NSLayoutAttributeRight
multiplier:1
constant:10]
So that they take up as much space as they can and if there is not enough space for the original font size it will be lowered thanks to adjustsFontSizeToFitWidth so that no text is truncated.
My problem is that when one needs to lower its font size due to long text i want the other label to lower its font size as well so that both are the same size instead of one being perhaps twice the size of the other. I would like to constraint the font size as well to match but alas i do not know how to this, any ideas?

From the UILabel documentation on adjustsFontSizeToWidth:
Normally, the label text is drawn with the font you specify in the font property. If this property is set to YES, however, and the text in the text property exceeds the label’s bounding rectangle, the receiver starts reducing the font size until the string fits or the minimum font size is reached.
I infer from this that the updated font is calculated at drawing time, and the font property is only read, not written to. Therefore, I believe the suggestion by Andrew to use KVO on the font property will not work.
Consequently, to achieve the result you want, you'll need to calculate the adjusted font size.
As Jackson notes in the comments, this very convenient NSString method to get the actual font has been deprecated in iOS 7. Technically, you could still use it until it's removed.
Another alternative is to loop through font scales until you find one that will fit both labels. I was able to get it working fine; here's a sample project that shows how I did it.
Also, here's the code in case that link ever stops working:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_rightLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:_leftLabel
attribute:NSLayoutAttributeRight
multiplier:1
constant:10];
[self.view addConstraint:constraint];
}
- (IBAction)makeRightLabelLongerPressed:(id)sender {
self.rightLabel.text = #"some much longer right label text";
}
- (IBAction)adjustLabelSizes:(id)sender {
NSLog(#"Attempting to adjust label sizes…");
CGFloat minimumScaleFactor = fmaxf(self.rightLabel.minimumScaleFactor, self.leftLabel.minimumScaleFactor);;
UIFont * startingFont = self.rightLabel.font;
for (double currentScaleFactor = 1.0; currentScaleFactor > minimumScaleFactor; currentScaleFactor -= 0.05) {
UIFont *font = [startingFont fontWithSize:startingFont.pointSize * currentScaleFactor];
NSLog(#" Attempting font with scale %f (size = %f)…", currentScaleFactor, font.pointSize);
BOOL leftLabelWorks = [self wouldThisFont:font workForThisLabel:self.leftLabel];
BOOL rightLabelWorks = [self wouldThisFont:font workForThisLabel:self.rightLabel];
if (leftLabelWorks && rightLabelWorks) {
NSLog(#" It fits!");
self.leftLabel.font = font;
self.rightLabel.font = font;
return;
} else {
NSLog(#" It didn't fit. :-(");
}
}
NSLog(#" It won't fit without violating the minimum scale (%f), so set both to minimum. Some text may get truncated.", minimumScaleFactor);
UIFont *minimumFont = [self.rightLabel.font fontWithSize:self.rightLabel.font.pointSize * self.rightLabel.minimumScaleFactor];
self.rightLabel.font = minimumFont;
self.leftLabel.font = minimumFont;
}
- (BOOL) wouldThisFont:(UIFont *)testFont workForThisLabel:(UILabel *)testLabel {
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:testFont, NSFontAttributeName, nil];
NSAttributedString *as = [[NSAttributedString alloc] initWithString:testLabel.text attributes:attributes];
CGRect bounds = [as boundingRectWithSize:CGSizeMake(CGRectGetWidth(testLabel.frame), CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin) context:nil];
BOOL itWorks = [self doesThisSize:bounds.size fitInThisSize:testLabel.bounds.size];
return itWorks;
}
- (BOOL)doesThisSize:(CGSize)aa fitInThisSize:(CGSize)bb {
if ( aa.width > bb.width ) return NO;
if ( aa.height > bb.height ) return NO;
return YES;
}
This approach could be trivially refactored into a category method that replaces the deprecated method linked to by Jackson.

You can solve this problem this way:
Swift 5
extension UILabel {
var actualFontSize: CGFloat {
guard let attributedText = attributedText else { return font.pointSize }
let text = NSMutableAttributedString(attributedString: attributedText)
text.setAttributes([.font: font as Any], range: NSRange(location: 0, length: text.length))
let context = NSStringDrawingContext()
context.minimumScaleFactor = minimumScaleFactor
text.boundingRect(with: frame.size, options: .usesLineFragmentOrigin, context: context)
let adjustedFontSize: CGFloat = font.pointSize * context.actualScaleFactor
return adjustedFontSize
}
}
Usage:
firstLabel.text = firstText
secondLabel.text = secondText
view.setNeedsLayout()
view.layoutIfNeeded()
let smallestSize = min(firstLabel.actualFontSize, secondLabel.actualFontSize)
firstLabel.font = firstLabel.font.withSize(smallestSize)
secondLabel.font = secondLabel.font.withSize(smallestSize)

You could try using key-value observing to observe changes to the font property on one label and when it does, set the other label to use the same font.
In your -viewDidLoad method:
// Add self as an observer of _rightLabel's font property
[_rightLabel addObserver:self forKeyPath:#"font" options:NSKeyValueObservingOptionNew context:NULL];
In the same controller implementation (self in the above code snippets context):
// Observe changes to the font property of _rightLabel
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == _rightLabel && [keyPath isEqualToString:#"font"]) {
// Set _leftLabel's font property to be the new value set on _rightLabel
_leftLabel.font = change[NSKeyValueChangeNewKey];
}
}

I found a better solution. Just add to ur text spaces from begin to end to uilabel, which have less text length. And font will same.

Related

Adjusting the height of the UITextField with editing using iOS Autolayout

How do we increase the height of UITextField while editing using autolayout. I have a UIScrollView with four UITextField's. Currently all the textfield's are all of constant height. I need only a particular textfield to grow its height based on the text while editing.
You need to set height constrain for each text field.
And on edit did end method you need to identify the textfield by tag and than change the constant of the appropriate textfield.
for ex. heightTxt1,heightTxt2,heightTxt3,heightTxt4 are constraint.
Than change while edit did end called for textfield 1
heightTxt1.constant= (count height on the basis of text and font style,size);
This should resolve your issue,
Step 1: Set no of lines in your label as 0
Step 2: Add this method in your class file
//-- Dynamic label frame depend on text
-(CGRect)getLabelHeightForIndex:(UILabel *)label label_string:(NSString *)label_string
{
CGSize maxSize = CGSizeMake(label.frame.size.width, 999);
CGRect contentFrame;
CGSize contentStringSize;
NSString *version = [[UIDevice currentDevice] systemVersion];
NSString *contentStr = label_string;
if ([version floatValue] < 7.0)
{
contentStringSize = [contentStr sizeWithFont:label.font constrainedToSize:maxSize lineBreakMode:label.lineBreakMode];
}
else
{
NSDictionary *contentDic = [NSDictionary dictionaryWithObjectsAndKeys:label.font, NSFontAttributeName, nil];
contentStringSize = [contentStr boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:contentDic context:nil].size;
}
contentFrame = CGRectMake(label.frame.origin.x, label.frame.origin.y, label.frame.size.width, contentStringSize.height);
return contentFrame;
}
Step 3: Access dynamic frame for label using following code
//-- Get dynamic label height from given string
CGRect contentFrame = [self getLabelHeightForIndex:Label label_string:Label.text];
Label.frame = contentFrame;
CGFloat LabelHeight = contentFrame.size.height;
First off, you will want to store the height constraint for that specific UITextField in a property.
For example: #property (nonatomic, weak) IBOutlet NSLayoutConstraint *specificTextFieldHeightConstraint;
Then, implement -[UITextFieldDelegate textFieldDidBeginEditing:] inside your UIViewController subclass:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
specificTextFieldHeightConstraint.constant = expandedHeightGoesHere;
}
Finally, you probably want to return the height back to normal by implementing -[UITextFieldDelegate textFieldDidEndEditing:] inside your UIViewController subclass:
- (void)textFieldDidEndEditing:(UITextField *)textField {
specificTextFieldHeightConstraint.constant = originalHeightConstant;
}
If you set equal heights constraints across all of the UITextFields, you will need to set the priority of the equal height constraint that is attached to specificTextField to low when editing, and set it back to high when editing ends. You will still need a height constraint on specificTextField, but it should have a lower priority than the equal height constraint when specificTextField is not editing, and it should have a higher priority than the equal height constraint when specificTextField is editing.

Can't seem to calculate correct line-height of NSAttributedString for a UILabel

I'm using NSAttributedString's boundingRectWithSize:options:context: method to calculate the expected height for some text, after reading this article from objc.io
+(CGSize)postLabelSizeForPost:(ANKPost *)post
{
CGFloat labelWidth = 220.0f;
NSAttributedString *text = [BXTPostCell mutableAttributedStringForPost:post];
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading;
CGRect boundingRect = [text boundingRectWithSize:CGSizeMake(labelWidth, CGFLOAT_MAX)
options:options
context:nil];
CGFloat height = (CGFloat) (ceil(boundingRect.size.height));
return CGSizeMake(labelWidth, height);
}
However, when I use this method, the text doesn't actually fit inside this size - the size is consistently too short.
At first, I'd thought the issue was with emoji characters - but it seems to be happening for many tableviewcells in the app.
Update:
I've talked with a few folks, and it sounds like the issue may be an underlying bug with boundingRectWithSize:options:context:. I figured out a workaround: create a dummy UILabel, give it the same attributes as the UILabel in the tableview cell (numberOfLines, etc) and then use sizeThatFits: to calculate the size. It's a hack, to be sure - but it solves my problem sufficiently. I wish it were more elegant, though!
I came across this same effect when trying to calculate the size of attributed text for a UITextView. I ran across this answer to another question.
The trick is to use NSLayoutManager's usedRectForTextContainer:. What is neat is that this method doesn't need to be used in conjunction with a UITextView. This means that you can use it off of the mainThread, key for pre-calculation of UITableViewCell heights.
Here's how I use it:
- (CGSize)rectForAttributedString:(NSAttributedString *)string withSize:(CGSize)theSize
{
if (!string || CGSizeEqualToSize(theSize, CGSizeZero)) {
return CGSizeZero;
}
// setup TextKit stack
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:theSize];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:string];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
// query for size
CGRect rect = [layoutManager usedRectForTextContainer:textContainer];
return CGSizeMake(ceilf(rect.size.width), ceilf(rect.size.height));
}
To make things easier, I created a convenience method to figure out the height for a specific width:
- (CGFloat)textViewHeightForAttributedString:(NSAttributedString *)string withWidth:(CGFloat)width
{
return [self rectForAttributedString:string withSize:CGSizeMake(width, CGFLOAT_MAX)].height;
}
Hope this helps.
PS. I'm kind of new at this whole programming thing so if my code can be optimized please let me know.
I think, you might not set preferredMaxLayoutWidth.
This property set constraint to the max width of a view.

UITextView Inside UITableViewCell Auto-Layout iOS 6 vs. iOS 7

I'm building a messaging screen as part of the app I'm writing. Currently, it is a UITableView containing cells that are my own custom subclass of UITableViewCell. I am using auto layout with constraints on the cell defined in Interface Builder. My messaging mimics, or attempts to mimic, the default messaging app. There are three main components of each table view cell: a UITextView containing the message body and two additional UILabels, one for the sender's name and/or time stamp, and the other for delivered/read receipts.
Now, using auto layout combined with tableView:heightForRowAtIndexPath: on my view controller, the message's text view in each table view cell is supposed to grow according to how large the message is (I use sizeWithFont:constainedToSize:lineBreakMode at present - I know it's deprecated but the replacements don't work on iOS 6 and are also flaky as of yet). This works fine when both labels and the text view are all present on the UI. However, in an individual message thread, I remove the delivered/read label using removeFromSuperview for all message cells but the final message (if said final message is sent by you). This does not cause adverse affects on iOS 7, but on iOS 6, any cell that has a label removed causes the text view to have a height of 0.0 (confirmed by debug outputs). Programmatically re-adding the label and appropriate auto layout constraints seems to fix it, but in any cell where that label is removed, even if I calculate a positive height for the text view in tableView:heightForRowAtIndexPath:, the text view height is zero, and the remaining label ends up shifted upwards to 'appear' to overwrite the text view.
I guess removing a view from its superview is the main culprit here, but I don't understand why this would occur only on iOS 6 instead of both 6 and 7.
Now, the code. Here is my cellForRowAtIndexPath: and heightForRowAtIndexPath: methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * sentMessageCellIdentifier = #"sentMessageCell";
static NSString * receivedMessageCellIdentifier = #"receivedMessageCell";
MessageCell * cell;
Message * messageObject = [associatedThread.messages objectAtIndex:indexPath.row];
GroupMember * selfMm = [associatedThread.parentGroup groupMemberForUser:[ApplicationInstance getInstance].currentUser];
if ([messageObject.sender isEqualToGroupMember:selfMm]) {
// Sent
cell = (MessageCell *) [tableView dequeueReusableCellWithIdentifier:sentMessageCellIdentifier];
cell.sentTimeLabel.text = [UtilityFunctions messageFriendlyFormattedDateTimeForDate:messageObject.messageTime];
if ([messageObject isEqualToMessage:[associatedThread.messages lastObject]]) {
cell.deliveredReadByLabel.text = #"Sent";
} else {
cell.deliveredReadByLabel.text = nil;
}
} else {
// Received
cell = (MessageCell *) [tableView dequeueReusableCellWithIdentifier:receivedMessageCellIdentifier];
[cell setSenderAndDateTimeForSender:messageObject.sender date:messageObject.messageTime];
}
// Read by label
NSString * readByText = nil;
if (associatedThread.parentGroupMember == nil) {
// Group thread
if (messageObject.readBy.count == 0) {
if (![messageObject.sender isEqualToGroupMember:selfMm]) {
readByText = #"Read by: only you";
}
} else {
NSInteger readByCount = messageObject.readBy.count;
NSInteger toSubtract = [messageObject.sender isEqualToGroupMember:selfMm] ? 1 : 2;
if (readByCount == associatedThread.members.count - toSubtract) { // If everyone read it (minus you and the sender)
readByText = #"Read by everyone";
} else {
GroupMember * randRbm = [messageObject.readBy firstObject];
if (messageObject.readBy.count == 1) {
cell.deliveredReadByLabel.text = [NSString stringWithFormat:#"Read by: %#", randRbm.user.displayName];
} else if (messageObject.readBy.count > 1) {
cell.deliveredReadByLabel.text = [NSString stringWithFormat:#"Read by: %# + %d", randRbm.user.displayName, messageObject.readBy.count - 1];
}
cell.deliveredReadByLabel.userInteractionEnabled = YES;
[cell.deliveredReadByLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapReadByLabel:)]];
}
}
} else {
// One-on-one individual thread
if ([messageObject isEqualToMessage:[associatedThread.messages lastObject]] &&
[messageObject.sender isEqualToGroupMember:selfMm]) {
if (cell.deliveredReadByLabel.superview == nil) {
[cell.contentView addSubview:cell.deliveredReadByLabel];
// Auto-layout bindings
NSArray * constaints = #[[NSLayoutConstraint constraintWithItem:cell.deliveredReadByLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:cell.sentTimeLabel
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:1.0],
[NSLayoutConstraint constraintWithItem:cell.deliveredReadByLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:20.0],
[NSLayoutConstraint constraintWithItem:cell.deliveredReadByLabel
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:-20.0],
[NSLayoutConstraint constraintWithItem:cell.deliveredReadByLabel
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-5.0]
];
[cell addConstraints:constaints];
}
if (messageObject.readBy.count == 1) {
readByText = #"Read";
}
} else {
[cell.deliveredReadByLabel removeFromSuperview];
}
}
if (readByText != nil) {
cell.deliveredReadByLabel.text = readByText;
}
debugLog(#"%#", [messageObject isEqualToMessage:[associatedThread.messages lastObject]] ? #"YES" : #"NO");
debugLog(#"x,y [%f, %f] | w,h [%f, %f] - message view", cell.messageView.frame.origin.x, cell.messageView.frame.origin.y, cell.messageView.frame.size.width, cell.messageView.frame.size.height);
debugLog(#"x,y [%f, %f] | w,h [%f, %f] - sent time label", cell.sentTimeLabel.frame.origin.x, cell.sentTimeLabel.frame.origin.y, cell.sentTimeLabel.frame.size.width, cell.sentTimeLabel.frame.size.height);
debugLog(#"x,y [%f, %f] | w,h [%f, %f] - sender time label", cell.senderAndDateTimeLabel.frame.origin.x, cell.senderAndDateTimeLabel.frame.origin.y, cell.senderAndDateTimeLabel.frame.size.width, cell.senderAndDateTimeLabel.frame.size.height);
debugLog(#"x,y [%f, %f] | w,h [%f, %f] - delivered label", cell.deliveredReadByLabel.frame.origin.x, cell.deliveredReadByLabel.frame.origin.y, cell.deliveredReadByLabel.frame.size.width, cell.deliveredReadByLabel.frame.size.height);
// Message body
[UtilityFunctions setZeroInsetsForTextView:cell.messageView];
cell.messageView.text = messageObject.messageBody;
cell.messageView.scrollEnabled = YES;
cell.messageView.scrollEnabled = NO;
return cell;
}
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *) indexPath {
CGFloat totalHeight = 0.0;
Message * m = [associatedThread.messages objectAtIndex:indexPath.row];
// Top and bottom padding
totalHeight += 5.0 + 5.0;
// Height + padding between labels (and text view)
totalHeight += 14.0 + 14.0 + 1.0 + 1.0; // height + height + padding + padding
// Modify UI slightly if incoming message and one-on-one thread:
if (associatedThread.parentGroupMember != nil) {
totalHeight -= (14.0 + 1.0);
if ([m isEqualToMessage:[associatedThread.messages lastObject]]) {
if ([m.sender isEqualToGroupMember:[associatedThread.parentGroup groupMemberForUser:[ApplicationInstance getInstance].currentUser]]) {
totalHeight += (14.0 + 1.0);
}
}
}
NSString * bodyText = m.messageBody;
CGSize constraint = CGSizeMake(MESSAGE_TEXT_WIDTH_MAX, CGFLOAT_MAX);
CGSize sizeWithFont = [bodyText sizeWithFont:[UIFont systemFontOfSize:16.0] constrainedToSize:constraint lineBreakMode:NSLineBreakByWordWrapping];
totalHeight += sizeWithFont.height + 1.0; // 1.0 because iOS hates me
if ([m isEqualToMessage:[associatedThread.messages lastObject]]) {
debugLog(#"YES");
} else {
debugLog(#"NO");
}
debugLog(#"height: %f", totalHeight);
return totalHeight;
}
Here are the constraints I set in Interface Builder. Note the static width of the message text view:
And here is how it looks in iOS 6 (notes: the colors are for my own visual aid, it obviously will not stay that way, and simulator/device produces same results):
Here is the expected behavior in iOS 7, as I desire it to behave:
It's important to note the the height of the actual table view cell itself appears to be correct, but the text view is not adjusting accordingly, despite being able to I've tried adjusting my code in both methods above and attempted different techniques to no avail. I'm fairly certain that I need to use removeFromSuperview as that is the only way to both use auto layout and accommodate what I am trying to do. The delivered/read label gets removed under the following conditions:
There are two people in the message thread
The message is the latest message in the thread
That last message was sent by you
I know this is a terribly specific question, but if anyone has ideas as to why this occurs I would be grateful. Note that the text view is not editable, though it is selectable.
As always, thanks.
Edit: I also occasionally will get iOS 6 to throw Assertion failure in -[UITableViewCell layoutSublayersOfLayer:] even though my custom subclass doesn't implement that function. It has whined about constraints before, but that is a crap shoot to reproduce that error.
I solved this by giving in and just keeping both UILabels on the cell at all times. I'll just be clever about how I arrange the date and read bys. I'll leave this in case someone has a similar issue in the future.

how to set vertical space constraint in Auto layout with baseline?

there are two labels in view. my designer will offer me markman as following
However, I can only set the constraint in Xcode like following
I mean, the designer need me to align the two labels with baseline, however, I can set the space of two labels with frame. Is there a way to set space of two labels with baseline instead of label's frame using Autolayout?
thanks #Arkadiusz, #Fogmeister, I have implemented a NSLayoutConstraint category to do it.
#implementation NSLayoutConstraint (MarkmanSpace)
- (void)updateSpaceBetweenTopView:(UIView *)topView bottomView:(UIView *)bottomView {
UIFont *topViewFont = [self fontFromView:topView];
UIFont *bottomViewFont = [self fontFromView:bottomView];
if(topViewFont && bottomViewFont) {
CGFloat padding = fabs(topViewFont.descender) + fabs([self lowercaseGlyphTopBaselineHeightForFont:bottomViewFont]);
self.constant -= padding;
}
}
- (UIFont *)fontFromView:(UIView *)view {
UIFont *viewFont = nil;
if([view isKindOfClass:[UILabel class]]) {
viewFont = ((UILabel *)view).font;
}
return viewFont;
}
- (CGFloat)lowercaseGlyphTopBaselineHeightForFont:(UIFont *)font {
return font.ascender - font.xHeight;
}
#end
but there's a problem ,which is the font.xHeight is lower the actual height for Chinese Character,
Your designer wants you to use a baseline and an x-height, see http://en.wikipedia.org/wiki/Baseline_(typography). A format option for the x-height isn't available in Auto Layout (see NSLayoutFormatOptions), so I don't think it's possible.

UILabel - auto-size label to fit text?

Is it possible to auto-resize the UILabel box/bounds to fit the contained text?
(I don't care if it ends up larger than the display)
So if a user enters "hello" or "my name is really long i want it to fit in this box", it is never truncated and the label is 'widened' accordingly?
Please check out my gist where I have made a category for UILabel for something very similar, my category lets a UILabel stretch it's height to show all the content: https://gist.github.com/1005520
Or check out this post: https://stackoverflow.com/a/7242981/662605
This would stretch the height, but you can change it around easily to work the other way and stretch the width with something like this, which is I believe what you want to do:
#implementation UILabel (dynamicSizeMeWidth)
- (void)resizeToStretch{
float width = [self expectedWidth];
CGRect newFrame = [self frame];
newFrame.size.width = width;
[self setFrame:newFrame];
}
- (float)expectedWidth{
[self setNumberOfLines:1];
CGSize maximumLabelSize = CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX);
CGSize expectedLabelSize = [[self text] sizeWithFont:[self font]
constrainedToSize:maximumLabelSize
lineBreakMode:[self lineBreakMode]];
return expectedLabelSize.width;
}
#end
You could more simply use the sizeToFit method available from the UIView class, but set the number of lines to 1 to be safe.
iOS 6 update
If you are using AutoLayout, then you have a built in solution. By setting the number of lines to 0, the framework will resize your label appropriately (adding more height) to fit your text.
iOS 8 update
sizeWithFont: is deprecated so use sizeWithAttributes: instead:
- (float)expectedWidth{
[self setNumberOfLines:1];
CGSize expectedLabelSize = [[self text] sizeWithAttributes:#{NSFontAttributeName:self.font}];
return expectedLabelSize.width;
}
Using [label sizeToFit]; will achieve the same result from Daniels Category.
Although I recommend to use autolayout and let the label resize itself based on constraints.
If we want that UILabel should shrink and expand based on text size then storyboard with autolayout is best option. Below are the steps to achieve this
Steps
Put UILabel in view controller and place it wherever you want. Also put 0 for numberOfLines property of UILabel.
Give it Top, Leading and Trailing space pin constraint.
Now it will give warning, Click on the yellow arrow.
Click on Update Frame and click on Fix Misplacement. Now this UILabel will shrink if text is less and expand if text is more.
This is not as complicated as some of the other answers make it.
Pin the left and top edges
Just use auto layout to add constraints to pin the left and top sides of the label.
After that it will automatically resize.
Notes
Don't add constraints for the width and height. Labels have an intrinsic size based on their text content.
Thanks to this answer for help with this.
No need to set sizeToFit when using auto layout. My complete code for the example project is here:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
#IBAction func changeTextButtonTapped(sender: UIButton) {
myLabel.text = "my name is really long i want it to fit in this box"
}
}
If you want your label to line wrap then set the number of lines to 0 in IB and add myLabel.preferredMaxLayoutWidth = 150 // or whatever in code. (I also pinned my button to the bottom of the label so that it would move down when the label height increased.)
If you are looking for dynamically sizing labels inside a UITableViewCell then see this answer.
Use [label sizeToFit]; to adjust the text in UILabel
Here's what I am finding works for my situation:
1) The height of the UILabel has a >= 0 constraint using autolayout. The width is fixed.
2) Assign the text into the UILabel, which already has a superview at that point (not sure how vital that is).
3) Then, do:
label.sizeToFit()
label.layoutIfNeeded()
The height of the label is now set appropriately.
I created some methods based Daniel's reply above.
-(CGFloat)heightForLabel:(UILabel *)label withText:(NSString *)text
{
CGSize maximumLabelSize = CGSizeMake(290, FLT_MAX);
CGSize expectedLabelSize = [text sizeWithFont:label.font
constrainedToSize:maximumLabelSize
lineBreakMode:label.lineBreakMode];
return expectedLabelSize.height;
}
-(void)resizeHeightToFitForLabel:(UILabel *)label
{
CGRect newFrame = label.frame;
newFrame.size.height = [self heightForLabel:label withText:label.text];
label.frame = newFrame;
}
-(void)resizeHeightToFitForLabel:(UILabel *)label withText:(NSString *)text
{
label.text = text;
[self resizeHeightToFitForLabel:label];
}
#implementation UILabel (UILabel_Auto)
- (void)adjustHeight {
if (self.text == nil) {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.bounds.size.width, 0);
return;
}
CGSize aSize = self.bounds.size;
CGSize tmpSize = CGRectInfinite.size;
tmpSize.width = aSize.width;
tmpSize = [self.text sizeWithFont:self.font constrainedToSize:tmpSize];
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, aSize.width, tmpSize.height);
}
#end
This is category method. You must set text first, than call this method to adjust UILabel's height.
You can size your label according to text and other related controls using two ways-
For iOS 7.0 and above
CGSize labelTextSize = [labelText boundingRectWithSize:CGSizeMake(labelsWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : labelFont
}
context:nil].size;
before iOS 7.0 this could be used to calculate label size
CGSize labelTextSize = [label.text sizeWithFont:label.font
constrainedToSize:CGSizeMake(label.frame.size.width, MAXFLOAT)
lineBreakMode:NSLineBreakByWordWrapping];
// reframe other controls based on labelTextHeight
CGFloat labelTextHeight = labelTextSize.height;
If you do not want to calculate the size of the label's text than you can use -sizeToFit on the instance of UILabel as-
[label setNumberOfLines:0]; // for multiline label
[label setText:#"label text to set"];
[label sizeToFit];// call this to fit size of the label according to text
// after this you can get the label frame to reframe other related controls
Add missing constraints in storyboard.
Select UILabel in storyboard and set the attributes "Line" to 0.
Ref Outlet the UILabel to Controller.h with id:label
Controller.m and add [label sizeToFit]; in viewDidLoad
the sizeToFit() method doesn't play well with constraints, but as of iOS 9 this is all you need -
label.widthAnchor.constraint(equalToConstant: label.intrinsicContentSize.width).activate()
I had a huge problems with auto layout.
We have two containers inside table cell. Second container is resized depending on Item description (0 - 1000 chars), and row should be resized based on them.
The missing ingredient was bottom constraint for description.
I've changed bottom constraint of dynamic element from = 0 to >= 0.
Fits everytime! :)
name.text = #"Hi this the text I want to fit to"
UIFont * font = 14.0f;
CGSize size = [name.text sizeWithAttributes:#{NSFontAttributeName: font}];
nameOfAssessment.frame = CGRectMake(400, 0, size.width, 44);
nameOfAssessment.font = [UIFont systemFontOfSize:font];
you can show one line output then set property Line=0 and show multiple line output then set property Line=1 and more
[self.yourLableName sizeToFit];
There's also this approach:
[self.myLabel changeTextWithAutoHeight:self.myStringToAssignToLabel width:180.0f];

Resources