iOS7 UITextView contentsize.height alternative

我正在将一个应用程序从IOS 6.1移植到IOS 7。我使用的布局中有一个固定宽度的UITextView,但它的高度是基于其内容大小的。e.对于IOS 6.1,检查ContentSizHeight并将其设置为TextView的框架高度就足够了,但它在IOS 7上不起作用。

How can I then create a UITextView with a fixed width, but dynamic height based on the text it's showing?

NOTE: I'm creating these views from code, not with Interface Builder.

65362 次浏览

With this following code, you can change the height of your UITextView depending of a fixed width (it's working on iOS 7 and previous version) :

- (CGFloat)textViewHeightForAttributedText:(NSAttributedString *)text andWidth:(CGFloat)width
{
UITextView *textView = [[UITextView alloc] init];
[textView setAttributedText:text];
CGSize size = [textView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}

With this function, you will take a NSAttributedString and a fixed width to return the height needed.

If you want to calculate the frame from a text with a specific font you, need to use the following code :

- (CGSize)text:(NSString *)text sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size
{
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0"))
{
CGRect frame = [text boundingRectWithSize:size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:@{NSFontAttributeName:font}
context:nil];
return frame.size;
}
else
{
return [text sizeWithFont:font constrainedToSize:size];
}
}

You can add that SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO on your prefix.pch file in your project as:

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)

You can also replace the previous test SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) by :

if ([text respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)])‌

There are simplier solution, using this method:

+(void)adjustTextViewHeightToContent:(UITextView *)textView;
{
if([[UIDevice currentDevice].systemVersion floatValue] >= 7.0f){
textView.height = [textView.layoutManager usedRectForTextContainer:textView.textContainer].size.height+2*fabs(textView.contentInset.top);
}else{
textView.height = textView.contentSize.height;
}
}

UPD: working just for displaying text (isEditable = NO)

My final solution is based on HotJard's but includes both top and bottom insets of text container instead of using 2*fabs(textView.contentInset.top) :

- (CGFloat)textViewHeight:(UITextView *)textView
{
return ceilf([textView.layoutManager usedRectForTextContainer:textView.textContainer].size.height +
textView.textContainerInset.top +
textView.textContainerInset.bottom);
}

This worked for me for iOS6 and 7:

CGSize textViewSize = [self.myTextView sizeThatFits:CGSizeMake(self.myTextView.frame.size.width, FLT_MAX)];
self.myTextView.height = textViewSize.height;

Use this little function

-(CGSize) getContentSize:(UITextView*) myTextView{
return [myTextView sizeThatFits:CGSizeMake(myTextView.frame.size.width, FLT_MAX)];
}

In iOS7, UITextView uses NSLayoutManager to layout text:

// If YES, then the layout manager may perform glyph generation and layout for a given portion of the text, without having glyphs or layout for preceding portions.  The default is NO.  Turning this setting on will significantly alter which portions of the text will have glyph generation or layout performed when a given generation-causing method is invoked.  It also gives significant performance benefits, especially for large documents.
@property(NS_NONATOMIC_IOSONLY) BOOL allowsNonContiguousLayout;

disable allowsNonContiguousLayout to fix contentSize :

textView.layoutManager.allowsNonContiguousLayout = NO;
   _textView.textContainerInset = UIEdgeInsetsZero;
_textView.textContainer.lineFragmentPadding = 0;

Just don't forget the lineFragmentPadding

simple solution - textView.isScrollEnabled = false works perfect when inside another scroll view, or tableView cell with UITableViewAutomaticDimension

@Ana's, @Blake Hamilton's solution in swift.

var contentHeight: CGFloat = textView.sizeThatFits(textView.frame.size).height

The good thing for me was that this also returns the correct contentSize, when isScrollEnable is set to false. Setting to false returned the text view's frame size instead of the content size.