在UITableView中使用Auto Layout进行动态单元格布局和可变行高

如何在表格视图中使用UITableViewCell内的自动布局来让每个单元格的内容和子视图确定行高(本身/自动),同时保持平滑的滚动性能?

685948 次浏览

太长别读:不喜欢阅读?直接跳到GitHub上的示例项目:

概念描述

无论您为哪个iOS版本开发,下面的前两个步骤都适用。

1.设置和添加约束

在你的UITableViewCell子类中,添加约束,以便单元格的子视图的边缘固定在单元格内容视图的边缘(最重要的是固定在顶部和底部边缘)。注意:不要将子视图固定到单元格本身;只有单元格的#1!让这些子视图的内在内容大小驱动表格视图单元格内容视图的高度,方法是确保每个子视图垂直维度中的内容压缩电阻内容拥抱约束不被你添加的更高优先级的约束覆盖。(哈?点击这里。

请记住,我们的想法是让单元格的子视图垂直连接到单元格的内容视图,以便它们可以“施加压力”并使内容视图展开以适应它们。使用带有几个子视图的示例单元格,以下是约束中一些(不是全部!)需要的视觉说明:

表格视图单元格上约束的示例说明。

你可以想象,随着更多的文本被添加到上面示例单元格中的多行主体标签中,它将需要垂直增长以适应文本,这将有效地迫使单元格在高度上增长。(当然,你需要得到正确的约束才能正常工作!)

正确设置约束绝对是使用Auto Layout获得动态单元格高度的最难也是最重要的部分。如果你在这里犯了一个错误,它可能会阻止其他一切工作-所以慢慢来!我建议在代码中设置你的约束,因为你确切地知道哪些约束被添加在哪里,并且在出现问题时更容易调试。在代码中添加约束可以像使用布局锚点的Interface Builder一样简单,并且比使用布局锚点的Interface Builder更强大,或者GitHub上提供的一个很棒的开源API。

  • 如果你要在代码中添加约束,你应该在UITableViewCell子类的updateConstraints方法中执行一次。请注意,updateConstraints可能会被多次调用,因此为了避免多次添加相同的约束,请确保将你的约束添加代码包装在updateConstraints中,以检查布尔属性,例如didSetupConstraints(在运行约束添加代码一次后将其设置为YES)。另一方面,如果你有更新现有约束的代码(例如在某些约束上调整constant属性),请将其放在updateConstraints中,但在didSetupConstraints的检查之外,以便每次调用方法时都可以运行。

2.确定唯一表视图单元格重用标识符

对于单元格中的每一组唯一约束,请使用唯一的单元格重用标识符。换句话说,如果您的单元格有多个唯一布局,则每个唯一布局都应该收到自己的重用标识符。(当您的单元格变体具有不同数量的子视图,或者子视图以不同的方式排列时,您需要使用新的重用标识符。)

例如,如果您在每个单元格中显示一封电子邮件,您可能有4个唯一的布局:仅带有主题的消息、带有主题和正文的消息、带有主题和照片附件的消息以及带有主题、正文和照片附件的消息。每个布局都有实现它所需的完全不同的约束,因此,一旦单元格被初始化并且为这些单元格类型之一添加了约束,单元格应该获得特定于该单元格类型的唯一重用标识符。这意味着当您将单元格退出重用队列时,约束已经添加并准备好用于该单元格类型。

请注意,由于内在内容大小的差异,具有相同约束(类型)的单元格仍然可能具有不同的高度!不要将根本不同的布局(不同的约束)与由于内容大小不同而计算的不同视图框架(从相同的约束中解决)混淆。

  • 不要将具有完全不同约束集的单元格添加到同一个复用池(即使用相同的复用标识符),然后在每次出队后尝试删除旧约束并从头开始设置新约束。内部Auto Layout引擎不是为处理约束的大规模变化而设计的,你会看到大量性能问题。

iOS8-自我调整细胞

3.启用行高估计

要启用自调整表格视图单元格的大小,您必须设置表格视图的UITableViewAutomaticDimension的rowHeight属性。您还必须为估计的RowHeight属性赋值。设置这些属性后,系统使用Auto Layout来计算行的实际高度

苹果:使用自我调整表视图单元格

使用iOS8,Apple已经内化了以前在iOS8之前必须由您实现的大部分工作。为了允许自调整单元格大小机制工作,您必须首先将table视图的rowHeight属性设置为常量UITableView.automaticDimension。然后,您只需将table视图的estimatedRowHeight属性设置为非零值即可启用行高估计,例如:

self.tableView.rowHeight = UITableView.automaticDimension;self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is

这是为表格视图提供尚未在屏幕上的单元格行高度的临时估计/占位符。然后,当这些单元格即将在屏幕上滚动时,将计算实际行高度。为了确定每行的实际高度,表格视图会自动询问每个单元格其contentView需要的高度,这是基于内容视图的已知固定宽度(基于表格视图的宽度,减去任何额外的东西,如部分索引或附件视图)以及您添加到单元格内容视图和子视图的自动布局约束。确定此实际单元格高度后,将使用新的实际高度更新行的旧估计高度(并根据需要对表格视图的ContentSize/ContentOffset进行任何调整)。

一般来说,您提供的估计不必非常准确-它仅用于正确调整表格视图中滚动指示器的大小,并且当您在屏幕上滚动单元格时,表格视图可以很好地调整不正确估计的滚动指示器。您应该将表格视图(在viewDidLoad或类似中)上的estimatedRowHeight属性设置为一个常量值,即“平均”行高。只有当你的行高度具有极端的可变性(例如,相差一个数量级)并且在滚动时注意到滚动指示器“跳跃”时,你才应该费心实现#2来执行所需的最小计算,以返回每行更准确的估计值。

iOS7支持(自己实现自动调整单元格大小)

3.做布局通行证并获得单元格高度

首先,实例化表格视图单元格每个重用标识符一个实例的屏幕外实例,该单元格严格用于高度计算。(离屏幕意味着单元格引用存储在视图控制器的属性/ivar中,并且永远不会从tableView:cellForRowAtIndexPath:返回以使表格视图在屏幕上实际呈现。)接下来,单元格必须配置为如果要在表格视图中显示它将持有的确切内容(例如文本、图像等)。

然后,强制单元格立即布局其子视图,然后在UITableViewCellcontentView上使用systemLayoutSizeFittingSize:方法找出所需的单元格高度。使用UILayoutFittingCompressedSize获取适合单元格所有内容所需的最小大小。然后可以从tableView:heightForRowAtIndexPath:委托方法返回高度。

4.使用估计行高

如果您的表视图中有几十行以上的行,您会发现在第一次加载表视图时执行Auto Layout约束求解会很快使主线程陷入困境,因为在第一次加载时对每一行调用tableView:heightForRowAtIndexPath:(以计算滚动指示器的大小)。

从iOS7开始,你可以(也绝对应该)在表格视图上使用estimatedRowHeight属性。这会为表格视图提供尚未在屏幕上的单元格行高度的临时估计/占位符。然后,当这些单元格即将在屏幕上滚动时,将计算实际行高度(通过调用tableView:heightForRowAtIndexPath:),并将估计高度更新为实际高度。

一般来说,您提供的估计不必非常准确-它仅用于正确调整表格视图中滚动指示器的大小,并且当您在屏幕上滚动单元格时,表格视图可以很好地调整不正确估计的滚动指示器。您应该将表格视图(在viewDidLoad或类似中)上的estimatedRowHeight属性设置为一个常量值,即“平均”行高。只有当你的行高度具有极端的可变性(例如,相差一个数量级)并且在滚动时注意到滚动指示器“跳跃”时,你才应该费心实现#2来执行所需的最小计算,以返回每行更准确的估计值。

5.(如果需要)添加行高缓存

如果你已经完成了上述所有操作,并且在tableView:heightForRowAtIndexPath:中执行约束求解时仍然发现性能慢得令人无法接受,那么不幸的是,你需要为单元格高度实现一些缓存。(这是Apple工程师建议的方法。)一般的想法是让Autolayout引擎第一次解决约束,然后缓存该单元格的计算高度,并将缓存的值用于该单元格高度的所有未来请求。当然,诀窍是确保在发生任何可能导致单元格高度变化的情况时清除单元格的缓存高度-主要是当该单元格的内容发生变化或发生其他重要事件时(例如用户调整动态类型文本大小滑块)。

iOS7通用示例代码(有很多多汁的评论)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{// Determine which reuse identifier should be used for the cell at this// index path, depending on the particular layout required (you may have// just one, or may have many).NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.// Note that this method will init and return a new cell if there isn't// one available in the reuse pool, so either way after this line of// code you will have a cell with the correct constraints ready to go.UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];         
// Configure the cell with content for the given indexPath, for example:// cell.textLabel.text = someTextForThisCell;// ...    
// Make sure the constraints have been set up for this cell, since it// may have just been created from scratch. Use the following lines,// assuming you are setting up constraints from within the cell's// updateConstraints method:[cell setNeedsUpdateConstraints];[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the// preferredMaxLayoutWidth needs to be set correctly. Do it at this// point if you are NOT doing it within the UITableViewCell subclass// -[layoutSubviews] method. For example:// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);    
return cell;}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{// Determine which reuse identifier should be used for the cell at this// index path.NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse// identifier, creating a cell and storing it in the dictionary if one// hasn't already been added for the reuse identifier. WARNING: Don't// call the table view's dequeueReusableCellWithIdentifier: method here// because this will result in a memory leak as the cell is created but// never returned from the tableView:cellForRowAtIndexPath: method!UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];if (!cell) {cell = [[YourTableViewCellClass alloc] init];[self.offscreenCells setObject:cell forKey:reuseIdentifier];}    
// Configure the cell with content for the given indexPath, for example:// cell.textLabel.text = someTextForThisCell;// ...    
// Make sure the constraints have been set up for this cell, since it// may have just been created from scratch. Use the following lines,// assuming you are setting up constraints from within the cell's// updateConstraints method:[cell setNeedsUpdateConstraints];[cell updateConstraintsIfNeeded];
// Set the width of the cell to match the width of the table view. This// is important so that we'll get the correct cell height for different// table view widths if the cell's height depends on its width (due to// multi-line UILabels word wrapping, etc). We don't need to do this// above in -[tableView:cellForRowAtIndexPath] because it happens// automatically when the cell is used in the table view. Also note,// the final width of the cell may not be the width of the table view in// some cases, for example when a section index is displayed along// the right side of the table view. You must account for the reduced// cell width.cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Do the layout pass on the cell, which will calculate the frames for// all the views based on the constraints. (Note that you must set the// preferredMaxLayoutWidth on multiline UILabels inside the// -[layoutSubviews] method of the UITableViewCell subclass, or do it// manually at this point before the below 2 lines!)[cell setNeedsLayout];[cell layoutIfNeeded];
// Get the actual height required for the cell's contentViewCGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Add an extra point to the height to account for the cell separator,// which is added between the bottom of the cell's contentView and the// bottom of the table view cell.height += 1.0;
return height;}
// NOTE: Set the table view's estimatedRowHeight property instead of// implementing the below method, UNLESS you have extreme variability in// your row heights and you notice the scroll indicator "jumping"// as you scroll.- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{// Do the minimal calculations required to be able to return an// estimated row height that's within an order of magnitude of the// actual height. For example:if ([self isTallCellAtIndexPath:indexPath]) {return 350.0;} else {return 40.0;}}

示例项目

由于UILabels中包含动态内容的表视图单元格,这些项目是具有可变行高的表视图的完全工作示例。

Xamarin(C#/. NET)

如果您正在使用Xamarin,请查看由@陈志立组合的这个示例项目

如果你有一个自定义单元格,并且你想要一个或多个具有动态高度的UILabel,那么系统布局尺寸适配尺寸方法结合启用的自动布局会返回CGSizeZero,除非你将所有单元格约束从单元格移动到其内容视图(正如@TomSwift在这里建议的那样如何调整Superview以适应所有子视图的自动布局?)。

为此,您需要在自定义UITableViewCell实现中插入以下代码(感谢@Adrian)。

- (void)awakeFromNib{[super awakeFromNib];for (NSLayoutConstraint *cellConstraint in self.constraints) {[self removeConstraint:cellConstraint];id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;NSLayoutConstraint *contentViewConstraint =[NSLayoutConstraint constraintWithItem:firstItemattribute:cellConstraint.firstAttributerelatedBy:cellConstraint.relationtoItem:seccondItemattribute:cellConstraint.secondAttributemultiplier:cellConstraint.multiplierconstant:cellConstraint.constant];[self.contentView addConstraint:contentViewConstraint];}}

将@斯迈伯格的答案与此混合应该有效。

一个足够重要的问题,我只是偶然发现张贴作为答案。

@斯迈伯格的答案基本上是正确的。但是,如果您在自定义单元格类的layoutSubviews方法中有任何代码,例如设置preferredMaxLayoutWidth,那么它将不会使用此代码运行:

[cell.contentView setNeedsLayout];[cell.contentView layoutIfNeeded];

这让我困惑了一段时间。然后我意识到这是因为这些只是触发contentView上的layoutSubview,而不是单元格本身。

我的工作代码看起来像这样:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];[cell configureWithThirdPartyObject:self.app];[cell layoutIfNeeded];CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;return height;

请注意,如果您正在创建一个新单元格,我很确定您不需要调用setNeedsLayout,因为它应该已经设置好了。如果您保存对单元格的引用,您可能应该调用它。无论哪种方式,它都不会造成任何伤害。

如果您在使用单元格子类的地方设置preferredMaxLayoutWidth之类的东西,另一个提示。正如@斯迈伯格提到的那样,“您的表格视图单元格的宽度尚未固定为表格视图的宽度”。这是真的,如果您在子类中而不是在视图控制器中工作,则会遇到麻烦。但是,此时您可以使用表格宽度简单地设置单元格框架:

例如,在计算高度时:

self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];CGRect oldFrame = self.summaryCell.frame;self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);

(我碰巧缓存了这个特定的单元格以供重用,但这无关紧要)。

以防人们仍然对此有困难。我写了一篇关于将Autolayout与UITableViews利用Autolayout实现动态单元格高度以及一个开源组件一起使用的快速博客文章,以帮助使其更加抽象和易于实现。https://github.com/Raizlabs/RZCellSizeManager

只要你在单元格中的布局是好的。

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;}

更新:您应该使用iOS8中引入的动态调整大小。

我在一个类别中包装了@斯迈伯格的iOS7解决方案

我决定将这个聪明的解决方案包装成UICollectionViewCell+AutoLayoutDynamicHeightCalculation类别。

该类别还纠正了@野生猴子回答中概述的问题(从笔尖加载单元格并systemLayoutSizeFittingSize:返回CGRectZero

它不考虑任何缓存,但适合我现在的需求。随意复制、粘贴和破解它。

UI合集查看单元格+自动布局动态高度计算。h

#import <UIKit/UIKit.h>
typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);
/***  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.**  Many thanks to @smileyborg and @wildmonkey**  @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights*/@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)
/***  Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.**  @param name Name of the nib file.**  @return collection view cell for using to calculate content based height*/+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;
/***  Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass**  @param block Render the model data to your UI elements in this block**  @return Calculated constraint derived height*/- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;
/***  Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds*/- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;
@end

UI合集查看单元格+自动布局动态高度计算。m

#import "UICollectionViewCell+AutoLayout.h"
@implementation UICollectionViewCell (AutoLayout)
#pragma mark Dummy Cell Generator
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name{UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];[heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];return heightCalculationCell;}
#pragma mark Moving Constraints
- (void)moveInterfaceBuilderLayoutConstraintsToContentView{[self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {[self removeConstraint:constraint];id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItemattribute:constraint.firstAttributerelatedBy:constraint.relationtoItem:secondItemattribute:constraint.secondAttributemultiplier:constraint.multiplierconstant:constraint.constant]];}];}
#pragma mark Height
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block{return [self heightAfterAutoLayoutPassAndRenderingWithBlock:blockcollectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];}
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width{NSParameterAssert(block);
block();
[self setNeedsUpdateConstraints];[self updateConstraintsIfNeeded];
self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));
[self setNeedsLayout];[self layoutIfNeeded];
CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return calculatedSize.height;
}
@end

使用示例:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{[(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];}];return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);}

值得庆幸的是,我们不必在iOS8中做这种爵士乐,但现在就有了!

就像@刘德华一样,我遇到了一个足够重要的问题,我把这个作为答案发布。

我为@斯迈利堡的答案挣扎了一段时间。我遇到的问题是,如果你在IB中定义了原型单元格,并在IB中添加了额外的元素(UILabelsUIButtons等),当你用[[YourTableViewCellClass alloc] init]实例化单元格时,它不会实例化该单元格中的所有其他元素,除非你已经编写了这样做的代码。(我对initWithStyle有类似的经历。)

要让故事板实例化所有附加元素,您的单元格将获得[tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"](不是[tableView dequeueReusableCellWithIdentifier:forIndexPath:],因为这会引起有趣的问题)。

动态表查看单元格高度和自动布局

解决故事板Auto Layout问题的好方法:

- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {static RWImageCell *sizingCell = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];});
[sizingCell setNeedsLayout];[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];return size.height;}

以下是我的解决方案:

您需要在加载视图之前告诉TableViewestimatedHeight。否则它将无法像预期的那样运行。

Objective-c

- (void)viewWillAppear:(BOOL)animated {_messageField.delegate = self;_tableView.estimatedRowHeight = 65.0;_tableView.rowHeight = UITableViewAutomaticDimension;}

更新到Swift4.2

override func viewWillAppear(_ animated: Bool) {tableView.rowHeight = UITableView.automaticDimensiontableView.estimatedRowHeight = 65.0}

另一个“解决方案”:跳过所有这些挫折,而是使用UIScrollView来获得看起来和感觉上与UITableView相同的结果。

对我来说,这是一个痛苦的“解决方案”,在花了20多个小时试图构建像斯迈伯格建议的那样的东西并失败了几个月和三个版本的App Store版本之后。

我的看法是,如果你真的需要iOS7支持(对我们来说,这是必不可少的),那么技术太脆弱了,你会把头发拉出来尝试。而且UITableView通常是完全矫枉过正的,除非你使用一些高级行编辑功能和/或真的需要支持1000+“行”(在我们的应用程序中,它实际上永远不会超过20行)。

额外的好处是,与UITableView附带的所有委托废话和来回相比,代码变得异常简单。它只是viewOnLoad中的一个代码循环,看起来优雅且易于管理。

以下是一些关于如何做到这一点的提示:

  1. 使用Storyboard或nib文件,创建ViewController和关联的根视图。

  2. 将UIScrollView拖到根视图上。

  3. 为顶层视图添加约束顶部、底部、左侧和右侧约束,以便UIScrollView填充整个根视图。

  4. 在UIScrollView中添加一个UIView并将其称为“容器”。为UIScrollView(其父级)添加顶部、底部、左侧和右侧约束。关键技巧:还添加一个“等宽”约束来链接UIScrollView和UIView。

    注意:您将收到一个错误“滚动视图具有不明确的可滚动内容高度”,并且您的容器UIView的高度应为0像素。当应用程序运行时,这两个错误似乎都无关紧要。

  5. 为每个“单元”创建nib文件和控制器。使用UIView而不是UITableViewCell。

  6. 在根ViewController中,您基本上将所有“行”添加到容器UIView并以编程方式添加约束,将其左右边缘链接到容器视图,将其顶部边缘链接到容器视图顶部(对于第一个项目)或前一个单元格。然后将最后一个单元格链接到容器底部。

对我们来说,每个“行”都在一个nib文件中。所以代码看起来像这样:

class YourRootViewController {
@IBOutlet var container: UIView! //container mentioned in step 4
override func viewDidLoad() {        
super.viewDidLoad()
var lastView: UIView?for data in yourDataSource {
var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)UITools.addViewToTop(container, child: cell.view, sibling: lastView)lastView = cell.view//Insert code here to populate your cell}
if(lastView != nil) {container.addConstraint(NSLayoutConstraint(item: lastView!,attribute: NSLayoutAttribute.Bottom,relatedBy: NSLayoutRelation.Equal,toItem: container,attribute: NSLayoutAttribute.Bottom,multiplier: 1,constant: 0))}
///Add a refresh control, if you want - it seems to work fine in our app:var refreshControl = UIRefreshControl()container.addSubview(refreshControl!)}}

下面是ViewToTopUITools.add代码:

class UITools {///Add child to container, full width of the container and directly under sibling (or container if sibling nil):class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil){child.setTranslatesAutoresizingMaskIntoConstraints(false)container.addSubview(child)        
//Set left and right constraints so fills full horz width:        
container.addConstraint(NSLayoutConstraint(item: child,attribute: NSLayoutAttribute.Leading,relatedBy: NSLayoutRelation.Equal,toItem: container,attribute: NSLayoutAttribute.Left,multiplier: 1,constant: 0))        
container.addConstraint(NSLayoutConstraint(item: child,attribute: NSLayoutAttribute.Trailing,relatedBy: NSLayoutRelation.Equal,toItem: container,attribute: NSLayoutAttribute.Right,multiplier: 1,constant: 0))        
//Set vertical position from last item (or for first, from the superview):container.addConstraint(NSLayoutConstraint(item: child,attribute: NSLayoutAttribute.Top,relatedBy: NSLayoutRelation.Equal,toItem: sibling == nil ? container : sibling,attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,multiplier: 1,constant: 0))}}

到目前为止,我发现这种方法的唯一“问题”是UITableView在滚动时在视图顶部有一个很好的“浮动”部分标题的功能。除非你添加更多的编程,否则上述解决方案不会做到这一点,但对于我们的特殊情况,这个功能不是100%必不可少的,当它消失时也没有人注意到。

如果您想在单元格之间设置分频器,只需在自定义“单元格”的底部添加一个1像素高的UIView,看起来像一个分频器。

请务必打开“弹跳”和“垂直弹跳”以使刷新控件正常工作,因此它看起来更像是一个表格视图。

TableView在你的内容下显示一些空行和分隔符,如果它没有填满全屏,而这个解决方案没有。但就个人而言,我更喜欢那些空行不存在的情况——在单元格高度可变的情况下,对我来说,空行在那里总是看起来“有问题”。

这里希望其他程序员在浪费20多个小时试图在他们自己的应用程序中使用Table View解决问题之前阅读我的帖子。:)

上面的iOS8很简单:

override func viewDidLoad() {super.viewDidLoad()
self.tableView.estimatedRowHeight = 80self.tableView.rowHeight = UITableView.automaticDimension}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {return UITableView.automaticDimension}

但对于iOS7,关键是计算autolayout后的高度:

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {cell.setNeedsLayout()cell.layoutIfNeeded()let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0return height}

重要

  • 如果有多行标签,不要忘记将numberOfLines设置为0

  • 不要忘记label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

完整的示例代码是这里

我不得不使用动态视图(通过代码设置视图和约束),当我想设置preferredMaxLayoutWidth标签的宽度为0时。所以我有错误的单元格高度。

然后我加了

[cell layoutSubviews];

在执行

[cell setNeedsUpdateConstraints];[cell updateConstraintsIfNeeded];

之后,标签的宽度如预期,动态高度计算正确。

Swift中的另一个iOs7+iOs8解决方案

var cell2height:CGFloat=44
override func viewDidLoad() {super.viewDidLoad()theTable.rowHeight = UITableViewAutomaticDimensiontheTable.estimatedRowHeight = 44.0;}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {let cell =  tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCellcell2height=cell.contentView.heightreturn cell}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {if #available(iOS 8.0, *) {return UITableViewAutomaticDimension} else {return cell2height}}

可变高度UITableViewCell的Swift示例

更新到Swift 3

威廉·胡的Swift答案很好,但它帮助我在第一次学习做某事时有一些简单而详细的步骤。下面的例子是我在学习制作可变单元格高度的UITableView时的测试项目。我基于这个Swift的基本UITableView示例

完成的项目应该看起来像这样:

在此处输入图片描述

创建一个新项目

它可以只是一个单视图应用程序。

添加代码

将一个新的Swift文件添加到您的项目中。将其命名为MyCustomCell。此类将保存您添加到故事板单元格中的视图的出口。在这个基本示例中,每个单元格中只有一个标签。

import UIKitclass MyCustomCell: UITableViewCell {@IBOutlet weak var myCellLabel: UILabel!}

我们稍后会连接这个插座。

打开ViewController.swift并确保您有以下内容:

import UIKitclass ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// These strings will be the data for the table view cellslet animals: [String] = ["Ten horses:  horse horse horse horse horse horse horse horse horse horse ","Three cows:  cow, cow, cow","One camel:  camel","Ninety-nine sheep:  sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep","Thirty goats:  goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]
// Don't forget to enter this in IB alsolet cellReuseIdentifier = "cell"
@IBOutlet var tableView: UITableView!
override func viewDidLoad() {super.viewDidLoad()
// delegate and data sourcetableView.delegate = selftableView.dataSource = self
// Along with auto layout, these are the keys for enabling variable cell heighttableView.estimatedRowHeight = 44.0tableView.rowHeight = UITableViewAutomaticDimension}
// number of rows in table viewfunc tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return self.animals.count}
// create a cell for each table view rowfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCellcell.myCellLabel.text = self.animals[indexPath.row]return cell}
// method to run when table view cell is tappedfunc tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {print("You tapped cell number \(indexPath.row).")}}

重要提示:

  • 以下两行代码(以及自动布局)使可变单元格高度成为可能:

    tableView.estimatedRowHeight = 44.0tableView.rowHeight = UITableViewAutomaticDimension

Setup the storyboard

Add a Table View to your view controller and use auto layout to pin it to the four sides. Then drag a Table View Cell onto the Table View. And onto the Prototype cell, drag a Label. Use auto layout to pin the label to the four edges of the content view of the Table View Cell.

enter image description here

Important note:

  • Auto layout works together with the important two lines of code I mentioned above. If you don't use auto layout it isn't going to work.

Other IB settings

Custom class name and Identifier

Select the Table View Cell and set the custom class to be MyCustomCell (the name of the class in the Swift file we added). Also set the Identifier to be cell (the same string that we used for the cellReuseIdentifier in the code above.

enter image description here

Zero Lines for Label

Set the number of lines to 0 in your Label. This means multi-line and allows the label to resize itself based on its content.

enter image description here

Hook Up the Outlets

  • Control drag from the Table View in the storyboard to the tableView variable in the ViewController code.
  • Do the same for the Label in your Prototype cell to the myCellLabel variable in the MyCustomCell class.

Finished

You should be able to run your project now and get cells with variable heights.

Notes

  • This example only works for iOS 8 and after. If you are still needing to support iOS 7 then this won't work for you.
  • Your own custom cells in your future projects will probably have more than a single label. Make sure that you get everything pinned right so that auto layout can determine the correct height to use. You may also have to use vertical compression resistance and hugging. See this article for more about that.
  • If you are not pinning the leading and trailing (left and right) edges, you may also need to set the label's preferredMaxLayoutWidth so that it knows when to line wrap. For example, if you had added a Center Horizontally constraint to the label in the project above rather than pin the leading and trailing edges, then you would need to add this line to the tableView:cellForRowAtIndexPath method:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

See also

在我的情况下,我必须创建一个自定义单元格,其中包含来自服务器的图像,可以是任何宽度和高度。和两个具有动态大小(宽度和高度)的UILabel

我在这里通过autolayout和编程方式实现了同样的结果:

基本上在上面@斯迈利博格的回答有帮助,但system LayoutSizeFitting Size从来没有为我工作过,在我的方法中:

1.不使用自动行高计算属性。2.不使用估计高度3.不需要不必要的更新约束。4.不使用自动首选最大布局宽度。5.不使用system LayoutSizeFitting Size(应该有使用但不为我工作,我不知道它在内部做什么),而是我的方法-(浮点数)getViewHeight工作,我知道它在内部做什么。

当我使用几种不同的显示单元格的方式时,是否可以在UITableView单元格中具有不同的高度?

(对于Xcode 8. x/Xcode 9. x阅读底部)

注意Xcode 7. x中的以下问题,这可能是混淆的根源:

Interface Builder无法正确处理自动调整单元格大小的设置。即使您的约束绝对有效,IB仍然会抱怨并给您令人困惑的建议和错误。原因是IB不愿意按照您的约束要求更改行的高度(以便单元格适合您的内容)。相反,它保持行的高度不变,并开始建议您更改约束,这你应该忽略

例如,假设你已经设置好了一切,没有警告,没有错误,一切正常。

在此处输入图片描述

现在,如果您更改字体大小(在本例中,我将描述标签字体大小从17.0更改为18.0)。

在此处输入图片描述

由于字体大小增加,标签现在想要占用3行(在此之前它占用2行)。

如果Interface Builder按预期工作,它将调整单元格的高度以适应新的标签高度。然而,实际发生的是IB显示红色的自动布局错误图标,并建议您修改拥抱/压缩优先级。

在此处输入图片描述

您应该忽略这些警告。您可以*做的是手动更改行的高度(选择单元格>大小检查器>行高)。

在此处输入图片描述

我一次点击一次改变这个高度(使用向上/向下步进器),直到红色箭头错误消失!(你实际上会得到黄色警告,此时只需继续进行“更新帧”,它应该都可以工作)。

*请注意,您实际上不必在Interface Builder中解决这些红色错误或黄色警告-在运行时,一切都将正常工作(即使IB显示错误/警告)。只需确保在运行时在控制台日志中没有收到任何AutoLayout错误。

事实上,尝试始终更新IB中的行高非常烦人,有时几乎不可能(因为小数值)。

为了防止恼人的IB警告/错误,您可以选择所涉及的视图,并在Size Inspector中为属性Ambiguity选择Verify Position Only

在此处输入图片描述


Xcode 8. x/Xcode 9. x看起来(有时)做的事情与Xcode 7. x不同,但仍然是错误的。例如,即使compression resistance priority/hugging priority设置为必需(1000),Interface Builder也可能拉伸或剪切标签以适应单元格(而不是调整单元格高度以适应标签周围)。在这种情况下,它甚至可能不显示任何自动布局警告或错误。或者有时它完全按照Xcode 7. x所做的那样,如上所述。

假设您有一个带有子视图的单元格,并且您希望单元格的高度足够高以包含子视图+填充。

1)将子视图的底部约束设置为cell.contentView减去所需的填充。不要对单元格或cell.contentView本身设置约束。

2)设置tableView的rowHeight属性或tableView:heightForRowAtIndexPath:UITableViewAutomaticDimension

3)将tableView的estimatedRowHeight属性或tableView:estimatedHeightForRowAtIndexPath:设置为高度的最佳猜测值。

就这样了。

tableView.estimatedRowHeight = 343.0tableView.rowHeight = UITableViewAutomaticDimension

在此处输入图像描述

在我的例子中,填充是因为sectionHeader和sectionFoter高度,其中故事板允许我将其更改为最小1。所以在viewTitLoad方法中:

tableView.sectionHeaderHeight = 0tableView.sectionFooterHeight = 0

如果你以编程方式布局,下面是在Swift中使用锚点iOS10要考虑的问题。

有三个规则/步骤

第1号:在viewDi Load上设置表视图的这两个属性,第一个告诉表视图应该期望其单元格上的动态大小,第二个只是让应用程序计算滚动条指示器的大小,因此它有助于性能。

    tableView.rowHeight = UITableViewAutomaticDimensiontableView.estimatedRowHeight = 100

第2号:这很重要,你需要将子视图添加到内容视图,而不是单元格的视图,并使用它的布局将子视图锚定到顶部和底部,这是一个如何做到这一点的工作示例。

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {super.init(style: style, reuseIdentifier: reuseIdentifier)setUpViews()}
private func setUpViews() {
contentView.addSubview(movieImageView)contentView.addSubview(descriptionLabel)let marginGuide = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([movieImageView.heightAnchor.constraint(equalToConstant: 80),movieImageView.widthAnchor.constraint(equalToConstant: 80),movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),
descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)
])}

创建一个将添加子视图并执行布局的方法,在init方法中调用它。

方法3:不要调用方法:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {}

如果你这样做,你将覆盖你的实现。

遵循这3条规则为表视图中的动态单元格。

这是一个工作实现https://github.com/jamesrochabrun/MinimalViewController

要设置行高和估计行高的自动尺寸,请确保以下步骤使自动尺寸对单元格/行高布局有效。

  • 分配和实现表视图数据源和委托
  • UITableViewAutomaticDimension分配给rowHeight并估计RowHeight
  • 实现委托/dataSource方法(即heightForRowAt并返回值UITableViewAutomaticDimension

-

目标C:

// in ViewController.h#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
@property IBOutlet UITableView * table;
@end
// in ViewController.m
- (void)viewDidLoad {[super viewDidLoad];self.table.dataSource = self;self.table.delegate = self;
self.table.rowHeight = UITableViewAutomaticDimension;self.table.estimatedRowHeight = UITableViewAutomaticDimension;}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;}

Swift:

@IBOutlet weak var table: UITableView!
override func viewDidLoad() {super.viewDidLoad()
// Don't forget to set dataSource and delegate for tabletable.dataSource = selftable.delegate = self
// Set automatic dimensions for row height// Swift 4.2 onwardstable.rowHeight = UITableView.automaticDimensiontable.estimatedRowHeight = UITableView.automaticDimension

// Swift 4.1 and belowtable.rowHeight = UITableViewAutomaticDimensiontable.estimatedRowHeight = UITableViewAutomaticDimension
}


// UITableViewAutomaticDimension calculates height of label contents/textfunc tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {// Swift 4.2 onwardsreturn UITableView.automaticDimension
// Swift 4.1 and belowreturn UITableViewAutomaticDimension}

对于UITableviewCell中的标签实例

  • 设置行数=0(&换行符模式=截断尾部)
  • 设置所有约束(上,下,左)相对于其超级视图/单元格容器。
  • 可选:设置标签的最小高度,如果您希望标签覆盖的垂直区域最小,即使没有数据。

输入图片描述

说明:如果您有多个具有动态长度的标签(UIElements),则应根据其内容大小进行调整:为要以更高优先级扩展/压缩的标签调整“内容拥抱和压缩阻力优先级”。

我只是对rowHeightestimatedRowHeight的两个值做了一些愚蠢的尝试和错误,只是认为它可能会提供一些调试见解:

如果您将它们都设置为或仅设置estimatedRowHeight,您将获得所需的行为:

tableView.rowHeight = UITableViewAutomaticDimensiontableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

建议你尽最大努力得到正确的估计,但最终结果并没有什么不同。它只会影响你的表现。

在此处输入图片描述


如果你只设置行的高度即只做:

tableView.rowHeight = UITableViewAutomaticDimension

你的最终结果不会如你所愿:

输入图片描述


如果您将estimatedRowHeight设置为1或更小,那么无论rowHeight如何,您都将崩溃

tableView.rowHeight = UITableViewAutomaticDimensiontableView.estimatedRowHeight = 1

我崩溃了以下错误消息:

Terminating app due to uncaught exception'NSInternalInconsistencyException', reason: 'table view row heightmust not be negative - provided height for index path (<NSIndexPath:0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'...some other lines...
libc++abi.dylib: terminating with uncaught exception of typeNSException

关于@斯迈伯格接受的答案,我找到了

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

在某些约束不明确的情况下是不可靠的。最好通过使用下面UIView上的辅助类别强制布局引擎计算一个方向的高度:

-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{[self setNeedsLayout];[self layoutIfNeeded];CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];CGFloat h = size.height;return h;}

其中w:是表视图的宽度

只需在您的视控制器中添加这两个函数,它就会解决您的问题。在这里,list是一个字符串数组,其中包含您的每一行字符串。

 func tableView(_ tableView: UITableView,estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])
return (tableView.rowHeight)}
func calculateHeight(inString:String) -> CGFloat{let messageString = input.textlet attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]
let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)
let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)
let requredSize:CGRect = rectreturn requredSize.height}

如果你有一个长期字符串。例如不要有一个换行符。那么你可能会遇到一些问题。

“所谓的”修复被接受的答案和其他几个答案提及。你只需要添加

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

我发现Suragh的回答是最完整的,简洁,因此不会混淆。

虽然没有解释为什么,但这些更改是需要的。让我们这样做。

将以下代码放入项目中。

import UIKit
class ViewController: UIViewController {
lazy var label : UILabel = {let lbl = UILabel()lbl.translatesAutoresizingMaskIntoConstraints = falselbl.backgroundColor = .redlbl.textColor = .blackreturn lbl}()
override func viewDidLoad() {super.viewDidLoad()// step0: (0.0, 0.0)print("empty Text intrinsicContentSize: \(label.intrinsicContentSize)")// ----------// step1: (29.0, 20.5)label.text = "hiiiii"print("hiiiii intrinsicContentSize: \(label.intrinsicContentSize)")// ----------// step2: (328.0, 20.5)label.text = "translatesAutoresizingMaskIntoConstraints"print("1 translate intrinsicContentSize: \(label.intrinsicContentSize)")// ----------// step3: (992.0, 20.5)label.text = "translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints"print("3 translate intrinsicContentSize: \(label.intrinsicContentSize)")// ----------// step4: (328.0, 20.5)label.text = "translatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints"print("3 translate w/ line breaks (but the line breaks get ignored, because numberOfLines is defaulted to `1` and it will force it all to fit into one line! intrinsicContentSize: \(label.intrinsicContentSize)")// ----------// step5: (328.0, 61.0)label.numberOfLines = 0print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)")// ----------// step6: (98.5, 243.5)label.preferredMaxLayoutWidth = 100print("3 translate w/ line breaks | '0' numberOfLines | preferredMaxLayoutWidth: 100 intrinsicContentSize: \(label.intrinsicContentSize)")
setupLayout()}func setupLayout(){view.addSubview(label)label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = truelabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true}}

请注意,我没有添加任何大小约束。我只添加了centerX、centerY约束。但是标签的大小仍然是正确的为什么?

因为contentSize

为了更好地处理这个问题,首先保留step0,然后注释掉步骤1-6。让setupLayout()留下来。观察行为。

然后取消注释步骤1,并观察。

然后取消注释第2步并观察。

这样做,直到你取消注释所有6个步骤并观察他们的行为。

从这一切可以得出什么结论?什么因素可以改变contenSize

  1. 文本长度:如果你有一个较长的文本,那么你的intrinsicContentSize的宽度将增加
  2. 换行符:如果您添加\n,则intrinsicContentSize的宽度将是所有行的最大宽度。如果一行有25个字符,另一行有2个字符,另一行有21个字符,那么您的宽度将根据25个字符计算
  3. 允许的行数:您必须将numberOfLines设置为0,否则您将没有多行。您的numberOfLines将调整您的intrinsicContentSize的高度
  4. 做调整:想象一下,基于你的文本,你的intrinsicContentSize的宽度是200,高度是100,但是你想限制标签容器的宽度--你打算怎么做?解决方案是将其设置为所需的宽度。你通过将#2设置为130来做到这一点,那么你的新intrinsicContentSize将有大约130的宽度。高度显然会超过100,因为你需要更多的行。话虽如此,如果你的约束设置正确,那么你根本不需要使用它!有关更多信息,请参阅这个答案及其注释。如果你没有限制宽度/高度的约束,你只需要使用preferredMaxLayoutWidth,就像有人可能会说“除非它超过preferredMaxLayoutWidth,否则不要包装文本”。但100%确定,如果你将前导/尾随和numberOfLines设置为0,那么你就很好了!长话短说,这里推荐使用它的大多数答案都是错误的!你不需要它。需要它表明你的约束设置不正确,或者你只是没有约束

  5. 另请注意,如果你增加字体大小,那么intrinsicContentSize的高度将会增加。我没有在我的代码中显示它。你可以自己尝试。

所以回到你的tableViewCell示例:

所有你需要做的是:

  • numberOfLines设置为0
  • 将标签正确地限制在边距/边缘
  • 不需要设置preferredMaxLayoutWidth
swift 4
@IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!@IBOutlet weak var tableView: UITableView!private var context = 1override func viewDidLoad() {super.viewDidLoad()
self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)}// Added observer to adjust tableview height based on the content
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {if context == &self.context{if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize{print("-----")print(size.height)tableViewHeightConstraint.constant = size.height + 50}}}
//Remove observerdeinit {
NotificationCenter.default.removeObserver(self)
}

如果单元格高度是动态的,你应该精确地计算它,然后在单元格呈现之前返回高度值。一个简单的方法是在表格视图单元格代码中定义计数方法,以便控制器在表格单元格高度委托方法中调用。如果高度依赖于表格或屏幕的宽度,不要忘记计算实单元帧宽(默认为320)。即在表格单元格高度委托方法中,首先使用cell.frame纠正单元格宽度,然后调用单元格中定义的计数高度方法以获取合适的值并返回

PS。生成单元格对象的代码可以在另一个方法中定义,用于调用不同的表视图单元格委托方法。

UITableView.automaticDimension可以通过Interface Builder设置:

Xcode>故事板>尺寸检查器

表格查看单元格>行高>自动

大小检查器