如何在iOS 7中计算UITableViewCell中带有UITextView的高度?
我在类似的问题上找到了很多答案,但是每个解决方案都涉及到sizeWithFont:
方法,而这个方法已经被弃用了!
我知道我必须使用- (CGFloat)tableView:heightForRowAtIndexPath:
方法,但是如何计算我的TextView需要显示整个文本的高度呢?
如何在iOS 7中计算UITableViewCell中带有UITextView的高度?
我在类似的问题上找到了很多答案,但是每个解决方案都涉及到sizeWithFont:
方法,而这个方法已经被弃用了!
我知道我必须使用- (CGFloat)tableView:heightForRowAtIndexPath:
方法,但是如何计算我的TextView需要显示整个文本的高度呢?
首先,需要注意的是,UITextView和UILabel在文本渲染方面有很大的区别。UITextView不仅具有所有边框的插图,而且其中的文本布局略有不同。
因此,使用sizeWithFont:
方法来计算UITextView的尺寸是不好的。
相反,UITextView本身有一个名为sizeThatFits:
的函数,该函数将返回显示整个UITextView内容所需的最小尺寸,并且您可以指定一个边界框。
以下方法适用于iOS 7及早期版本,目前没有包含任何过时的方法。
- (CGFloat)textViewHeightForAttributedText: (NSAttributedString*)text andWidth: (CGFloat)width {
UITextView *calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:text];
CGSize size = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}
该函数将接受一个NSAttributedString
和所需宽度作为CGFloat
,并返回所需的高度。
由于我最近做了类似的事情,我想分享一些我遇到的相关问题的解决方案。希望能对某些人有所帮助。
这是更深入的内容,将涵盖以下内容:
UITextView
的完整内容所需的大小时:设置UITableViewCell
的高度UITableViewCell
的大小时,保持光标在可见区域内,并保持第一个响应者位于UITextView
上如果您正在使用静态表视图或仅具有已知数量的UITextView
,则可以使步骤2变得更加简单。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// check here, if it is one of the cells, that needs to be resized
// to the size of the contained UITextView
if ( )
return [self textViewHeightForRowAtIndexPath:indexPath];
else
// return your normal height here:
return 100.0;
}
在您的UITableViewController
子类中添加一个NSMutableDictionary
(本示例中称为textViews
)作为实例变量。
使用此字典来存储各个UITextView
的引用,如下所示:
(是的,indexPaths是字典的有效键)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Do you cell configuring ...
[textViews setObject:cell.textView forKey:indexPath];
[cell.textView setDelegate: self]; // Needed for step 3
return cell;
}
此函数现在将计算实际高度:
- (CGFloat)textViewHeightForRowAtIndexPath: (NSIndexPath*)indexPath {
UITextView *calculationView = [textViews objectForKey: indexPath];
CGFloat textViewWidth = calculationView.frame.size.width;
if (!calculationView.attributedText) {
// This will be needed on load, when the text view is not inited yet
calculationView = [[UITextView alloc] init];
calculationView.attributedText = // get the text from your datasource add attributes and insert here
textViewWidth = 290.0; // Insert the width of your UITextViews or include calculations to set it accordingly
}
CGSize size = [calculationView sizeThatFits:CGSizeMake(textViewWidth, FLT_MAX)];
return size.height;
}
对于接下来的两个功能,重要的是将UITextView的代理设置为您的UITableViewController。如果您需要其他内容作为代理,可以通过从那里进行相关调用或使用适当的NSNotificationCenter挂钩来解决它。
- (void)textViewDidChange:(UITextView *)textView {
[self.tableView beginUpdates]; // This will cause an animated update of
[self.tableView endUpdates]; // the height of your UITableViewCell
// If the UITextView is not automatically resized (e.g. through autolayout
// constraints), resize it here
[self scrollToCursorForTextView:textView]; // OPTIONAL: Follow cursor
}
- (void)textViewDidBeginEditing:(UITextView *)textView {
[self scrollToCursorForTextView:textView];
}
如果光标不在UITableView
可见的范围之内,这将使UITableView
滚动到光标的位置:
- (void)scrollToCursorForTextView: (UITextView*)textView {
CGRect cursorRect = [textView caretRectForPosition:textView.selectedTextRange.start];
cursorRect = [self.tableView convertRect:cursorRect fromView:textView];
if (![self rectVisible:cursorRect]) {
cursorRect.size.height += 8; // To add some space underneath the cursor
[self.tableView scrollRectToVisible:cursorRect animated:YES];
}
}
在编辑时,您的UITableView
的部分可能会被键盘覆盖。 如果没有调整表视图的插入内容,scrollToCursorForTextView:
将无法滚动到您在表视图底部的光标位置。
- (void)keyboardWillShow:(NSNotification*)aNotification {
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.tableView.contentInset.top, 0.0, kbSize.height, 0.0);
self.tableView.contentInset = contentInsets;
self.tableView.scrollIndicatorInsets = contentInsets;
}
- (void)keyboardWillHide:(NSNotification*)aNotification {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.35];
UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.tableView.contentInset.top, 0.0, 0.0, 0.0);
self.tableView.contentInset = contentInsets;
self.tableView.scrollIndicatorInsets = contentInsets;
[UIView commitAnimations];
}
最后一部分:
在你的视图加载时,通过 NSNotificationCenter
订阅键盘变化的通知:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
正如Dave Haupert指出的那样,我忘记包括rectVisible
函数:
- (BOOL)rectVisible: (CGRect)rect {
CGRect visibleRect;
visibleRect.origin = self.tableView.contentOffset;
visibleRect.origin.y += self.tableView.contentInset.top;
visibleRect.size = self.tableView.bounds.size;
visibleRect.size.height -= self.tableView.contentInset.top + self.tableView.contentInset.bottom;
return CGRectContainsRect(visibleRect, rect);
}
我还注意到, scrollToCursorForTextView:
中仍包含直接引用我项目中的一个TextField的代码。如果您遇到bodyTextView
未被找到的问题,请检查更新后的函数版本。
现在有一个新的函数可以替换sizeWithFont,它叫boundingRectWithSize。
我将以下函数添加到我的项目中,在iOS7及以上版本上使用新函数,在低于iOS 7的版本上使用旧函数。这个函数基本上与sizeWithFont具有相同的语法:
-(CGSize)text:(NSString*)text sizeWithFont:(UIFont*)font constrainedToSize:(CGSize)size{
if(IOS_NEWER_OR_EQUAL_TO_7){
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
nil];
CGRect frame = [text boundingRectWithSize:size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributesDictionary
context:nil];
return frame.size;
}else{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return [text sizeWithFont:font constrainedToSize:size];
#pragma clang diagnostic pop
}
}
您可以将IOS_NEWER_OR_EQUAL_TO_7添加到项目中的prefix.pch文件中,如下所示:
#define IOS_NEWER_OR_EQUAL_TO_7 ( [ [ [ UIDevice currentDevice ] systemVersion ] floatValue ] >= 7.0 )
如果您正在使用UITableViewAutomaticDimension,我有一个非常简单的(仅限iOS 8)解决方案。在我的情况下,这是一个静态表视图,但我想您可以将其适应于动态原型...
我为文本视图的高度创建了约束输出口,并像这样实现了以下方法:
// Outlets
@property (weak, nonatomic) IBOutlet UITextView *textView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewHeight;
// Implementation
#pragma mark - Private Methods
- (void)updateTextViewHeight {
self.textViewHeight.constant = self.textView.contentSize.height + self.textView.contentInset.top + self.textView.contentInset.bottom;
}
#pragma mark - View Controller Overrides
- (void)viewDidLoad {
[super viewDidLoad];
[self updateTextViewHeight];
}
#pragma mark - TableView Delegate & Datasource
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 80;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
#pragma mark - TextViewDelegate
- (void)textViewDidChange:(UITextView *)textView {
[self.tableView beginUpdates];
[self updateTextViewHeight];
[self.tableView endUpdates];
}
但要记住:文本视图必须可滚动,并且您必须设置约束以便它们适用于自动调整大小:
最基本的单元格示例为:
Tim Bodeit的回答很好。我使用了Simple Solution的代码来正确获取文本视图的高度,并在heightForRowAtIndexPath中使用该高度。但是,我没有使用其他部分来调整文本视图的大小。相反,我编写了代码来在cellForRowAtIndexPath中更改文本视图的frame。
在iOS 6及以下版本中,一切都正常,但在iOS 7中,即使文本视图的frame确实被调整,文本视图中的文本也无法完全显示。(我没有使用自动布局)。这应该是因为在iOS 7中有TextKit,而文本的位置由UITextView中的NSTextContainer控制。因此,在我的情况下,我需要添加一行代码来设置someTextView,以使其在iOS 7中正常工作。
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) {
someTextView.textContainer.heightTracksTextView = YES;
}
设置:
UITextView
和其他内容。TableCell.h
相关联。UITableView
与TableViewController.h
相关联。解决方案:
(1) 在TableViewController.m
中添加:
// This is the method that determines the height of each cell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// I am using a helper method here to get the text at a given cell.
NSString *text = [self getTextAtIndex:indexPath];
// Getting the height needed by the dynamic text view.
CGSize size = [self frameForText:text sizeWithFont:nil constrainedToSize:CGSizeMake(300.f, CGFLOAT_MAX)];
// Return the size of the current row.
// 80 is the minimum height! Update accordingly - or else, cells are going to be too thin.
return size.height + 80;
}
// Think of this as some utility function that given text, calculates how much
// space would be needed to fit that text.
- (CGSize)frameForText:(NSString *)text sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size
{
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
nil];
CGRect frame = [text boundingRectWithSize:size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributesDictionary
context:nil];
// This contains both height and width, but we really care about height.
return frame.size;
}
// Think of this as a source for the text to be rendered in the text view.
// I used a dictionary to map indexPath to some dynamically fetched text.
- (NSString *) getTextAtIndex: (NSIndexPath *) indexPath
{
return @"This is stubbed text - update it to return the text of the text view.";
}
(2)添加到TableCell.m
:
// This method will be called when the cell is initialized from the storyboard
// prototype.
- (void)awakeFromNib
{
// Assuming TextView here is the text view in the cell.
TextView.scrollEnabled = YES;
}
解释:
这里发生的情况是:每个文本视图都通过垂直和水平约束与表格单元格的高度绑定 - 这意味着当表格单元格高度增加时,文本视图也会增加其大小。我使用了 @manecosta 的修改版本代码来计算适合在单元格中放置给定文本的文本视图所需的高度。因此,这意味着给定具有 X 个字符的文本,frameForText:
将返回一个大小,该大小具有与文本视图所需高度匹配的 size.height
属性。
现在,所有剩下的就是更新单元格的高度以匹配所需的文本视图的高度。这是在 heightForRowAtIndexPath:
中实现的。如评论中所述,由于 size.height
只是文本视图而不是整个单元格的高度,因此应添加一些偏移量。在这个例子中,这个值是80。
dream.dream
是我在文本视图中呈现的文本。 - Zorayr@protocol TextInputTableViewCellDelegate <NSObject>
@optional
- (void)textInputTableViewCellTextWillChange:(TextInputTableViewCell *)cell;
- (void)textInputTableViewCellTextDidChange:(TextInputTableViewCell *)cell;
@end
@interface TextInputTableViewCell : UITableViewCell
@property (nonatomic, weak) id<TextInputTableViewCellDelegate> delegate;
@property (nonatomic, readonly) UITextView *textView;
@property (nonatomic) NSInteger minLines;
@property (nonatomic) CGFloat lastRelativeFrameOriginY;
@end
#import "TextInputTableViewCell.h"
@interface TextInputTableViewCell () <UITextViewDelegate> {
NSLayoutConstraint *_heightConstraint;
}
@property (nonatomic) UITextView *textView;
@end
@implementation TextInputTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.selectionStyle = UITableViewCellSelectionStyleNone;
_textView = [UITextView new];
_textView.translatesAutoresizingMaskIntoConstraints = NO;
_textView.delegate = self;
_textView.scrollEnabled = NO;
_textView.font = CELL_REG_FONT;
_textView.textContainer.lineFragmentPadding = 0.0;
_textView.textContainerInset = UIEdgeInsetsZero;
[self.contentView addSubview:_textView];
[self.contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[view]-|" options:nil metrics:nil views:@{@"view": _textView}]];
[self.contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[view]-|" options:nil metrics:nil views:@{@"view": _textView}]];
_heightConstraint = [NSLayoutConstraint constraintWithItem: _textView
attribute: NSLayoutAttributeHeight
relatedBy: NSLayoutRelationGreaterThanOrEqual
toItem: nil
attribute: NSLayoutAttributeNotAnAttribute
multiplier: 0.0
constant: (_textView.font.lineHeight + 15)];
_heightConstraint.priority = UILayoutPriorityRequired - 1;
[_textView addConstraint:_heightConstraint];
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
self.minLines = 1;
}
- (void)setMinLines:(NSInteger)minLines {
_heightConstraint.constant = minLines * _textView.font.lineHeight + 15;
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([self.delegate respondsToSelector:@selector(textInputTableViewCellTextWillChange:)]) {
[self.delegate textInputTableViewCellTextWillChange:self];
}
return YES;
}
- (void)textViewDidChange:(UITextView *)textView {
if ([self.delegate respondsToSelector:@selector(textInputTableViewCellTextDidChange:)]) {
[self.delegate textInputTableViewCellTextDidChange:self];
}
}
@interface SomeTableViewController () <TextInputTableViewCellDelegate>
@end
@implementation SomeTableViewController
. . . . . . . . . . . . . . . . . . . .
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TextInputTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: TextInputTableViewCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
cell.minLines = 3;
. . . . . . . . . .
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (void)textInputTableViewCellWillChange:(TextInputTableViewCell *)cell {
cell.lastRelativeFrameOriginY = cell.frame.origin.y - self.tableView.contentOffset.y;
}
- (void)textInputTableViewCellTextDidChange:(TextInputTableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[UIView performWithoutAnimation:^{
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:indexPath];
}];
CGFloat contentOffsetY = cell.frame.origin.y - cell.lastRelativeFrameOriginY;
self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, contentOffsetY);
CGRect caretRect = [cell.textView caretRectForPosition:cell.textView.selectedTextRange.start];
caretRect = [self.tableView convertRect:caretRect fromView:cell.textView];
CGRect visibleRect = self.tableView.bounds;
visibleRect.origin.y += self.tableView.contentInset.top;
visibleRect.size.height -= self.tableView.contentInset.top + self.tableView.contentInset.bottom;
BOOL res = CGRectContainsRect(visibleRect, caretRect);
if (!res) {
caretRect.size.height += 5;
[self.tableView scrollRectToVisible:caretRect animated:NO];
}
}
@end
Here minLines
allows to set minimum height for the textView (to
resist height minimizing by AutoLayout with
UITableViewAutomaticDimension).
moveRowAtIndexPath:indexPath:
with the same indexPath starts
tableViewCell height re-calculation and re-layout.
performWithoutAnimation:
removes side-effect (tableView content
offset jumping on starting new line while typing).
It is important to preserve relativeFrameOriginY
(not
contentOffsetY
!) during cell update because contentSize
of the
cells before the current cell could be change by autoLayout calculus
in unexpected way. It removes visual jumps on system hyphenation
while typing long words.
Note that you shouldn't set the property estimatedRowHeight
! The
following doesn't work
self.tableView.estimatedRowHeight = UITableViewAutomaticDimension;
Use only tableViewDelegate method.
==========================================================================
如果不介意tableView和tableViewCell之间的弱绑定以及从tableViewCell更新tableView的几何形状,那么可以升级上面的TextInputTableViewCell
类。@interface TextInputTableViewCell : UITableViewCell
@property (nonatomic, weak) id<TextInputTableViewCellDelegate> delegate;
@property (nonatomic, weak) UITableView *tableView;
@property (nonatomic, readonly) UITextView *textView;
@property (nonatomic) NSInteger minLines;
@end
#import "TextInputTableViewCell.h"
@interface TextInputTableViewCell () <UITextViewDelegate> {
NSLayoutConstraint *_heightConstraint;
CGFloat _lastRelativeFrameOriginY;
}
@property (nonatomic) UITextView *textView;
@end
@implementation TextInputTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.selectionStyle = UITableViewCellSelectionStyleNone;
_textView = [UITextView new];
_textView.translatesAutoresizingMaskIntoConstraints = NO;
_textView.delegate = self;
_textView.scrollEnabled = NO;
_textView.font = CELL_REG_FONT;
_textView.textContainer.lineFragmentPadding = 0.0;
_textView.textContainerInset = UIEdgeInsetsZero;
[self.contentView addSubview:_textView];
[self.contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[view]-|" options:nil metrics:nil views:@{@"view": _textView}]];
[self.contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[view]-|" options:nil metrics:nil views:@{@"view": _textView}]];
_heightConstraint = [NSLayoutConstraint constraintWithItem: _textView
attribute: NSLayoutAttributeHeight
relatedBy: NSLayoutRelationGreaterThanOrEqual
toItem: nil
attribute: NSLayoutAttributeNotAnAttribute
multiplier: 0.0
constant: (_textView.font.lineHeight + 15)];
_heightConstraint.priority = UILayoutPriorityRequired - 1;
[_textView addConstraint:_heightConstraint];
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
self.minLines = 1;
self.tableView = nil;
}
- (void)setMinLines:(NSInteger)minLines {
_heightConstraint.constant = minLines * _textView.font.lineHeight + 15;
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
_lastRelativeFrameOriginY = self.frame.origin.y - self.tableView.contentOffset.y;
return YES;
}
- (void)textViewDidChange:(UITextView *)textView {
NSIndexPath *indexPath = [self.tableView indexPathForCell:self];
if (indexPath == nil) return;
[UIView performWithoutAnimation:^{
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:indexPath];
}];
CGFloat contentOffsetY = self.frame.origin.y - _lastRelativeFrameOriginY;
self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, contentOffsetY);
CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.start];
caretRect = [self.tableView convertRect:caretRect fromView:self.textView];
CGRect visibleRect = self.tableView.bounds;
visibleRect.origin.y += self.tableView.contentInset.top;
visibleRect.size.height -= self.tableView.contentInset.top + self.tableView.contentInset.bottom;
BOOL res = CGRectContainsRect(visibleRect, caretRect);
if (!res) {
caretRect.size.height += 5;
[self.tableView scrollRectToVisible:caretRect animated:NO];
}
}
@end
您单元格的高度将由UILabel的内容计算,但所有文本都将通过TextField显示。
如果你想根据内部UITextView
的高度自动调整UITableViewCell
的高度,请参考我在这里的答案:https://dev59.com/3G445IYBdhLWcg3wXZS9#45890087
解决方案非常简单,应该适用于iOS 7及以上版本。确保在StoryBoard中UITableViewCell
内部的UITextView
的Scrolling Enabled
选项已经关闭off。
然后在你的UITableViewController
的viewDidLoad()
方法中设置tableView.rowHeight = UITableViewAutomaticDimension
和tableView.estimatedRowHeight > 0
,如下所示:
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44.0
}
就是这样。 UITableViewCell
的高度将根据内部的 UITextView
的高度自动调整。
Swift 版本
func textViewHeightForAttributedText(text: NSAttributedString, andWidth width: CGFloat) -> CGFloat {
let calculationView = UITextView()
calculationView.attributedText = text
let size = calculationView.sizeThatFits(CGSize(width: width, height: CGFloat.max))
return size.height
}
attributedText
会导致NSAttributedString属性的默认值被设置到textView上。在我的情况下,这会导致相同文本的textview高度不同。 - Alexander