How do I size a UITextView to its content on iOS 7?
Solution 1
I favor this minimal code change: Just add these two lines after addSubview
and before grabbing the height
of the frame
...
[scrollView1 addSubview: myTextView];
[myTextView sizeToFit]; //added
[myTextView layoutIfNeeded]; //added
CGRect frame = myTextView.frame;
...
This is tested backwards compatible with iOS 6. NOTE that it shrink-wraps the width. If you're just interested in the height and have a fixed width, just grab the new height but set the original width, and it works just as before on both iOS 6 and 7.
(Speculation: it does size to fit on iOS 7 as well, but the layout is updated later or in a separate thread, and that this forces the layout immediately so that its frame is updated in time for using its height value a few lines later in the same thread.)
NOTES:
1) You might or might not have implemented the outer container resize this way. It does seem to be a common snippet, though, and I've used it in my projects.
2) Since sizeToFit
seems to work as expected on iOS 7, you likely don't need the premature addSubView. Whether it will still work on iOS 6 then is untested by me.
3) Speculation: The extra layoutIfNeeded
mid-thread might be costly. The alternative as I see it is to resize the outer container on the layout callback (fired or not depending on if the OS decides whether layout is needed or not) where the outer container resize will cause another layout update. Both updates might be combined with other layout updates to be more efficient. If you do have such a solution and you can show that it is more efficient, add it as answer and I'll be sure to mention it here.
Solution 2
Since I'm using Auto Layout, I use the value of [textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height
to update the constant
of the textView
's height UILayoutConstraint
.
Solution 3
I use an adapted version of madmik's answer that eliminates the fudge factor:
- (CGFloat)measureHeightOfUITextView:(UITextView *)textView
{
if ([textView respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)])
{
// This is the code for iOS 7. contentSize no longer returns the correct value, so
// we have to calculate it.
//
// This is partly borrowed from HPGrowingTextView, but I've replaced the
// magic fudge factors with the calculated values (having worked out where
// they came from)
CGRect frame = textView.bounds;
// Take account of the padding added around the text.
UIEdgeInsets textContainerInsets = textView.textContainerInset;
UIEdgeInsets contentInsets = textView.contentInset;
CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + textView.textContainer.lineFragmentPadding * 2 + contentInsets.left + contentInsets.right;
CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;
frame.size.width -= leftRightPadding;
frame.size.height -= topBottomPadding;
NSString *textToMeasure = textView.text;
if ([textToMeasure hasSuffix:@"\n"])
{
textToMeasure = [NSString stringWithFormat:@"%@-", textView.text];
}
// NSString class method: boundingRectWithSize:options:attributes:context is
// available only on ios7.0 sdk.
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
NSDictionary *attributes = @{ NSFontAttributeName: textView.font, NSParagraphStyleAttributeName : paragraphStyle };
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
CGFloat measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
return measuredHeight;
}
else
{
return textView.contentSize.height;
}
}
Solution 4
Based on other answers, I made it work(in Swift). This solves the problem with newline character.
textView.sizeToFit()
textView.layoutIfNeeded()
let height = textView.sizeThatFits(CGSizeMake(textView.frame.size.width, CGFloat.max)).height
textView.contentSize.height = height
Auto Layout is needed.
Solution 5
If you're using Auto Layout, you could create a trivial UITextView
subclass that self-sizes the text view height to fit the content:
@interface ContentHeightTextView : UITextView
@end
@interface ContentHeightTextView ()
@property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
@end
@implementation ContentHeightTextView
- (void)layoutSubviews
{
[super layoutSubviews];
CGSize size = [self sizeThatFits:CGSizeMake(self.bounds.size.width, FLT_MAX)];
if (!self.heightConstraint) {
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:size.height];
[self addConstraint:self.heightConstraint];
}
self.heightConstraint.constant = size.height;
[super layoutSubviews];
}
@end
Of course, the text view's width and position must be defined by additional constraints configured elsewhere in the program.
If you create this custom text view in IB, give the text view a height constraint in order to satisfy Xcode; just make sure the height constraint created in IB is merely a placeholder (i.e., tick the box that says "Remove at build time").
An alternative way to implement the UITextView
subclass is as follows (this implementation might qualify as best practice):
@interface ContentHeightTextView ()
@property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
@end
@implementation ContentHeightTextView
- (void)layoutSubviews
{
[super layoutSubviews];
[self setNeedsUpdateConstraints];
}
- (void)updateConstraints
{
CGSize size = [self sizeThatFits:CGSizeMake(self.bounds.size.width, FLT_MAX)];
if (!self.heightConstraint) {
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:size.height];
[self addConstraint:self.heightConstraint];
}
self.heightConstraint.constant = size.height;
[super updateConstraints];
}
@end
![Henrik Erlandsson](https://i.stack.imgur.com/5wGzF.jpg?s=256&g=1)
Henrik Erlandsson
Before 2010, I worked in my own company as web designer and developer. 2010-2015: iPhone and Android app and web developer using Objective-C, Java, Lua, HTML5, CSS3, PHP, Javascript, XML, with dozens of apps and dozens of websites to my name. Since 2015, a systems developer with 5 years experience in databases, AJAX and SQL, writing business systems to order for clients and accompanying apps and websites e.g. reporting, charting, inventory, backend and some frontend. Outside my professional career, I'm a published writer, artist, and composer.
Updated on January 24, 2021Comments
-
Henrik Erlandsson over 3 years
I've been using the accepted answer here for years.
On iOS 7, the contentSize.height becomes the frame.height-8, regardless of text content.
What's a working method to adjust the height on iOS 7?
-
Piyuesh over 10 yearsIt resizes textview but I still didn't manage to resize textview's parent element with this in iOS 7, can you please share more details ?
-
Henrik Erlandsson over 10 yearsNo idea, too little information. But if you write a question with the code that doesn't work I could have a look.
-
sbauch over 10 yearsthanks, this is useful. was hoping to dynamically resize as I type though. I guess the best I can ask for is to put the sizing logic in the delegate's textFieldDidEndEditing ?
-
dobiho over 10 yearsI can solved content height of UITextView is iOS 7 from tames code. Thanks.
-
Henrik Erlandsson about 10 years@JannieT since I posted the answer I've used it in every app I've updated on the app store, I guess five or so. Some text views were single line, some were multiline. I may be updating another today, could have a look on the very latest OS 7.1.x.
-
iamjustaprogrammer almost 10 yearsDoesn't adapt when the user hits return. New line clipped until you type in text.
-
Lapinou almost 10 yearsVery nice! And very helpful! :D Thanks!!
-
Alexander Volkov over 9 yearsMattDiPasquale, There should I put these code? Into viewDidLoad, layoutSubviews or else?
-
Legoless over 9 years@AlexanderVolkov layoutSubviews or viewWillAppear.
-
Asif Bilal over 9 yearsIt worked for me on iOS 7.0. But didn't check on iOS > 7.0. Thanks @phatmann.
-
phatmann over 9 yearsThe docs for
intrinsicContentSize
say:This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height.
So my code is not really kosher. On iOS 7 it worked, but it is not reliable on iOS 8. If you are using self-sizing cells on iOS 8, then you can usepreferredLayoutAttributesFittingAttributes:
-
Okhan Okbay about 9 yearsI think subclassing UITextView and working with height constraint is the best solution when dealing with auto-layout.Thank you.
-
Ninji almost 9 yearsworks great for me, thanks :) to avoid line clipped, simply use
[self.textView scrollRangeToVisible:NSMakeRange(0,0)];
when detected different line height -
Hobbyist over 8 yearsThis NSLayoutConstraint is being configured with a constant that exceeds internal limits. A smaller value will be substituted, but this problem should be fixed. Break on void _NSLayoutConstraintNumberExceedsLimit() to debug. This will be logged only once. This may break in the future. -[<_UITextTiledLayer: 0x7fd9a8dcfac0> display]: Ignoring bogus layer size (375.000000, 1000000000.000000), contentsScale 2.000000, backing store size (750.000000, 2000000000.000000)
-
Sandeep Bhandari over 7 years@mattdipasquale : No idea why this hasn't been marked as accepted answer :) But u sir :) Saved my day with this piece of code :) Hence up voted :)