Reputation: 2159
[self.Review sizeToFit];
Result before sizeToFit:
NSStringFromCGRect(self.Review.frame): {{90, 20}, {198, 63}}
Result After sizeToFit:
NSStringFromCGRect(self.Review.frame): {{90, 20}, {181, 45}}
I want the width to remain the same. I just want to change the height. THe automask is
(lldb) po self.Review
(UILabel *) $1 = 0x08bb0fe0 <UILabel: 0x8bb0fe0; frame = (90 20; 181 45); text = 'I'm at Mal Taman Anggrek ...'; clipsToBounds = YES; opaque = NO; autoresize = LM+RM+H; userInteractionEnabled = NO; layer = <CALayer: 0x8bb68b0>>
I know that there is a way to do so with: How to adjust and make the width of a UILabel to fit the text size?
The answers are either strange (we need to resupply the font information). Or unclear.
You will also need to define a maximum width, and tell your program what to do if sizeToFit gives you a width greater than that maximum.
I will use the strange solution of using sizeWithFont
. It's strange because UILabel already knows the font in the label.
Actually how does sizeToFit behave anyway? How does it decide whether we need thinner or taller UILabel?
Upvotes: 39
Views: 39685
Reputation: 939
I believe I have a much simpler solution:
let oldWidth = self.frame.size.width
label.sizeToFit()
label.frame.size.width = oldWidth
Simply store what the old width was, sizeToFit()
applies to both width and height, then reset the width back to it's old value leaving only the label's height changed.
Upvotes: 1
Reputation: 439
An easy extension for this problem for Swift 3.
extension UILabel {
func sizeToFitHeight() {
let size: CGSize = self.sizeThatFits(CGSize.init(width: self.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
var frame:CGRect = self.frame
frame.size.height = size.height
self.frame = frame
}
}
Upvotes: 5
Reputation: 80271
This is how it is done. Because the label already contains the font information, including it in this method call is trivial.
CGSize size = [label.text sizeWithFont:label.font
constrainedToSize:CGSizeMake(maxWidth, MAXFLOAT)
lineBreakMode:UILineBreakModeWordWrap];
CGRect labelFrame = label.frame;
labelFrame.size.height = size.height;
label.frame = labelFrame;
Swift version using the more up-to-date boundingRectWithSize
:
let maxHeight = CGFloat.infinity
let rect = label.attributedText?.boundingRectWithSize(CGSizeMake(maxWidth, maxHeight),
options: .UsesLineFragmentOrigin, context: nil)
var frame = label.frame
frame.size.height = rect.size.height
label.frame = frame
Upvotes: 41
Reputation: 12277
c = CGSize(width: yourWidth, height: CGFloat.greatestFiniteMagnitude)
result = sizeThatFits(c).height
So...
let t = .... your UITextView
let w = .... your known width of the item
let trueHeight = t.sizeThatFits(
CGSize(width: t, height: CGFloat.greatestFiniteMagnitude)).height
That's it.
Very often, you want to know the difference in height compared to a "normal" one-line entry.
This comes up in particular if you are making some sort of cells that have nothing to do with UTableView (say, some sort of custom stack view). You have to set the height manually at some point. On your storyboard, build it as you wish, using a sample text of any short string (eg, "A") which of course will have only one line in any normal situation. Then you do something like this...
func setTextWithSizeCorrection() { // an affaire iOS
let t = .... your UITextView
let w = .... your known width of the item
let oneLineExample = "A"
let actualText = yourDataSource[row].description.whatever
// get the height as if with only one line...
experimental.text = oneLineExample
let oneLineHeight = t.sizeThatFits(
CGSize(width: w, height: CGFloat.greatestFiniteMagnitude)).height
// get the height with the actual long text...
// (note that usually, you do not want a one-line minimum)
experimental.text = (actualText == "") ? oneLineExample : actualText
let neededHeight = t.sizeThatFits(
CGSize(width: w, height: CGFloat.greatestFiniteMagnitude)).height
experimental.text = actualText
let delta = neededHeight - oneLineHeight
set the height of your cell, or whatever it is(standardHeight + delta)
}
Final point: one of the really stupid things in iOS is that UITextView has a bizarre sort of margin inside it. This means you can't just swap between UITextViews and labels; and it causes many other problems. Apple haven't fixed this issue as of writing. To fix the problem is difficult: the following approach is usually OK:
@IBDesignable class UITextViewFixed: UITextView {
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
textContainerInset = UIEdgeInsets.zero;
textContainer.lineFragmentPadding = 0;
}
}
Broken, unusable UITextView from Apple...
UITextViewFixed
which is, in most cases, an acceptable fix:
(Yellow added for clarity.)
Upvotes: 2
Reputation: 2660
FIXED SOLUTION FOR SWIFT 3
Use CGFloat.greatestFiniteMagnitude
for maxHeight.
let size = CGSize.init(width: yourLabel.frame.size.width,
height: CGFloat.greatestFiniteMagnitude)
let rect = errorLabel.attributedText?.boundingRect(with: size, options: .usesLineFragmentOrigin, context: nil)
var frame = errorLabel.frame
frame.size.height = (rect?.size.height)!
errorLabel.frame = frame
OR Create UIlabel
extension, and call method yourLabel. sizeToFitHeight()
extension UILabel {
func sizeToFitHeight() {
let maxHeight : CGFloat = CGFloat.greatestFiniteMagnitude
let size = CGSize.init(width: self.frame.size.width, height: maxHeight)
let rect = self.attributedText?.boundingRect(with: size, options: .usesLineFragmentOrigin, context: nil)
var frame = self.frame
frame.size.height = (rect?.size.height)!
self.frame = frame
}
}
Upvotes: 1
Reputation: 2341
Swift 3 version of Mundi's answer:
let maxHeight: CGFloat = 10000
let rect = label.attributedText!.boundingRect(with: CGSize(width: maxWidth, height: maxHeight),
options: .usesLineFragmentOrigin,
context: nil)
var frame = labe.frame
frame.size.height = rect.size.height
label.frame = frame
Upvotes: 0
Reputation: 3099
Agreed API would benefit from this addition. Make your life easier and add an extension to the UILabel class in Swift as follows:
extension UILabel {
func sizeToFitHeight() {
let size:CGSize = self.sizeThatFits(CGSizeMake(self.frame.size.width, CGFloat.max))
var frame:CGRect = self.frame
frame.size.height = size.height
self.frame = frame
}
}
Upvotes: 2
Reputation: 818
You can achieve the same result with sizeThatFits.
CGSize size = [label sizeThatFits:CGSizeMake(label.frame.size.width, CGFLOAT_MAX)];
CGRect frame = label.frame;
frame.size.height = size.height;
label.frame = frame;
Or alternatively, with sizeToFit.
CGRect frame = label.frame;
[label sizeToFit];
frame.size.height = label.frame.size.height;
label.frame = frame;
Upvotes: 44
Reputation: 8527
I think you should not use NSString
's size...
methods, UILabel
already has an API for just that, as @vatrif points out (which internally probably uses just NSString
's methods).
Nevertheless I think UILabel
's API could be improved. Thats why I think a category would be the most elegant solution:
// UILabel+Resize.h
@interface UILabel (Resize)
- (void)sizeToFitHeight;
@end
// UILabel+Resize.m
@implementation UILabel (Resize)
- (void)sizeToFitHeight {
CGSize size = [self sizeThatFits:CGSizeMake(self.frame.size.width, CGFLOAT_MAX)];
CGRect frame = self.frame;
frame.size.height = size.height;
self.frame = frame;
}
@end
Just one thing: I am not sure about the appropriate name for that method:
Comments very much appreciated, thanks!
Upvotes: 2
Reputation: 260
sizeToFit works great. The only problem is that it is based on the current size of the control.
For instance if you are recycling table view cells with a label, the label may have been reduced in width in a previous cell and so, the call to sizeToFit may look unreliable.
Just reset the original width of your control before you send the sizeToFit message.
Upvotes: 17