提问人:smileyborg 提问时间:9/12/2013 最后编辑:Krunalsmileyborg 更新时间:6/21/2023 访问量:703569
在UITableView中使用自动布局进行动态单元格布局和可变行高
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
答:
TL;DR:不喜欢读书?直接跳转到 GitHub 上的示例项目:
- iOS 8 示例项目 - 需要 iOS 8
- iOS 7 示例项目 - 适用于 iOS 7+
概念描述
无论您针对哪个 iOS 版本进行开发,下面的前 2 个步骤都适用。
1. 设置和添加约束
在子类中,添加约束,以便单元格的子视图的边缘固定到单元格的 contentView 的边缘(最重要的是固定到顶部和底部边缘)。注意:不要将子视图固定到单元格本身,而只能固定到单元格的 contentView
!通过确保每个子视图的垂直维度中的内容压缩阻力和内容拥抱约束不会被已添加的更高优先级约束覆盖,让这些子视图的固有内容大小驱动表视图单元格的内容视图的高度。(咦?点击这里。UITableViewCell
)
请记住,这个想法是将单元格的子视图垂直连接到单元格的内容视图,以便它们可以“施加压力”并使内容视图扩展以适合它们。使用一个包含几个子视图的示例单元格,下面是一些(不是全部)约束需要是什么样子的直观说明:
您可以想象,随着更多文本被添加到上面示例单元格中的多行正文标签中,它将需要垂直增长以适合文本,这将有效地迫使单元格的高度增长。(当然,您需要正确约束才能正常工作!
正确约束绝对是使用自动布局获得动态单元格高度的最困难和最重要的部分。如果你在这里犯了一个错误,它可能会阻止其他一切工作——所以慢慢来!我建议在代码中设置约束,因为您确切地知道在何处添加了哪些约束,并且在出现问题时调试起来要容易得多。在代码中添加约束与使用布局锚点或 GitHub 上提供的出色开源 API 之一的 Interface Builder 一样简单,并且功能强大得多。
- 如果要在代码中添加约束,则应在 UITableViewCell 子类的方法中执行此操作一次。请注意,可能会多次调用,因此为避免多次添加相同的约束,请确保将添加约束的代码包装在对布尔属性的检查中,例如(在运行约束添加代码一次后将其设置为 YES)。另一方面,如果您有更新现有约束的代码(例如调整某些约束的属性),请将其放在检查中,但放在检查之外,以便每次调用该方法时都可以运行它。
updateConstraints
updateConstraints
updateConstraints
didSetupConstraints
constant
updateConstraints
didSetupConstraints
2. 确定唯一的表视图单元格重用标识符
对于单元格中的每组唯一约束,请使用唯一的单元重用标识符。换言之,如果单元格具有多个唯一布局,则每个唯一布局都应收到自己的重用标识符。(当单元格变体具有不同数量的子视图,或者子视图以不同的方式排列时,您需要使用新的重用标识符的一个很好的提示。
例如,如果在每个单元格中显示一封电子邮件,则可能有 4 种独特的布局:仅包含主题的邮件、包含主题和正文的邮件、包含主题和照片附件的邮件,以及包含主题、正文和照片附件的邮件。每个布局都有实现它所需的完全不同的约束,因此,一旦初始化了单元格并为其中一种单元格类型添加了约束,单元格就应该获得特定于该单元格类型的唯一重用标识符。这意味着,当您将单元格取消排队以供重用时,约束已经添加并准备好用于该单元格类型。
请注意,由于内在内容大小的差异,具有相同约束(类型)的单元格可能仍具有不同的高度!不要由于内容大小不同而将根本不同的布局(不同约束)与不同的计算视图框架(从相同的约束求解)混淆。
- 不要将具有完全不同约束集的单元添加到同一个重用池中(即使用相同的重用标识符),然后在每次出列后尝试删除旧约束并从头开始设置新约束。内部自动布局引擎不是为处理约束的大规模更改而设计的,您将看到大量的性能问题。
对于 iOS 8 - 自调整单元格大小
3. 启用行高估计
要启用自调整大小的表视图单元格,必须将表视图的 rowHeight 属性设置为 UITableViewAutomaticDimension。您还必须 为 estimatedRowHeight 属性赋值。只要两者 设置了这些属性,系统使用自动布局来计算 行的实际高度
Apple:使用自调整大小的表格视图单元格
在 iOS 8 中,Apple 已经内化了以前在 iOS 8 之前必须由您实现的大部分工作。为了使自调整单元格大小的机制起作用,必须首先将表视图上的属性设置为常量。然后,只需通过将表视图的属性设置为非零值来启用行高估计,例如:rowHeight
UITableView.automaticDimension
estimatedRowHeight
self.tableView.rowHeight = UITableView.automaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
这样做的目的是为表格视图提供尚未出现在屏幕上的单元格的行高的临时估计值/占位符。然后,当这些单元格即将在屏幕上滚动时,将计算实际的行高。为了确定每行的实际高度,表格视图会根据内容视图的已知固定宽度(基于表格视图的宽度减去任何其他内容(如节索引或附件视图)以及已添加到单元格的内容视图和子视图的自动布局约束,自动询问每个单元格需要的高度。确定此实际单元格高度后,该行的旧估计高度将更新为新的实际高度(并且根据需要对表视图的 contentSize/contentOffset 进行任何调整)。contentView
一般来说,您提供的估计值不必非常准确 - 它仅用于正确调整表格视图中滚动指示器的大小,并且当您在屏幕上滚动单元格时,表格视图可以很好地调整滚动指示器以进行不正确的估计。应将表视图上的属性(在或类似)中设置为“平均”行高的常量值。只有当行高具有极端可变性(例如,相差一个数量级)并且您注意到滚动指示器在滚动时“跳跃”时,您才应该费心实现 tableView:estimatedHeightForRowAtIndexPath:
来执行所需的最小计算,以便为每行返回更准确的估计值。estimatedRowHeight
viewDidLoad
对于 iOS 7 支持(自行实现自动单元格大小调整)
3.进行布局传递并获取单元格高度
首先,实例化表视图单元格的屏幕外实例,每个重用标识符对应一个实例,该实例严格用于高度计算。(屏幕外意味着单元格引用存储在视图控制器上的属性/ivar 中,并且永远不会返回表视图以实际呈现在屏幕上。接下来,必须为单元格配置确切的内容(例如文本、图像等),如果要在表格视图中显示该单元格,该单元格将包含这些内容。tableView:cellForRowAtIndexPath:
然后,强制单元格立即布局其子视图,然后使用 上的方法找出单元格所需的高度。用于获得适合单元格所有内容所需的最小尺寸。然后,可以从委托方法返回高度。systemLayoutSizeFittingSize:
UITableViewCell
contentView
UILayoutFittingCompressedSize
tableView:heightForRowAtIndexPath:
4. 使用估计的行高
如果你的表格视图有几十行以上,你会发现,在第一次加载表格视图时,执行自动布局约束求解会很快使主线程陷入困境,就像第一次加载时对每一行所做的那样(为了计算滚动指示器的大小)。tableView:heightForRowAtIndexPath:
从 iOS 7 开始,您可以(并且绝对应该)在表视图上使用该属性。这样做的目的是为表格视图提供尚未出现在屏幕上的单元格的行高的临时估计值/占位符。然后,当这些单元格即将在屏幕上滚动时,将计算实际行高(通过调用),并将估计高度更新为实际高度。estimatedRowHeight
tableView:heightForRowAtIndexPath:
一般来说,您提供的估计值不必非常准确 - 它仅用于正确调整表格视图中滚动指示器的大小,并且当您在屏幕上滚动单元格时,表格视图可以很好地调整滚动指示器以进行不正确的估计。应将表视图上的属性(在或类似)中设置为“平均”行高的常量值。只有当行高具有极端可变性(例如,相差一个数量级)并且您注意到滚动指示器在滚动时“跳跃”时,您才应该费心实现 tableView:estimatedHeightForRowAtIndexPath:
来执行所需的最小计算,以便为每行返回更准确的估计值。estimatedRowHeight
viewDidLoad
5.(如果需要)添加行高缓存
如果您已经完成了上述所有操作,但仍然发现在进行约束求解时性能慢得令人无法接受,那么很遗憾,您需要为单元格高度实现一些缓存。(这是 Apple 工程师建议的方法。一般的想法是让自动布局引擎在第一次求解约束,然后缓存该单元格的计算高度,并将缓存的值用于该单元格高度的所有未来请求。当然,诀窍是确保在发生任何可能导致单元格高度更改的事情时清除单元格的缓存高度 - 主要是当该单元格的内容更改或其他重要事件发生时(例如用户调整动态类型文本大小滑块)。tableView:heightForRowAtIndexPath:
iOS 7 通用示例代码(包含大量有趣的注释)
- (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 contentView
CGFloat 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;
}
}
示例项目
- iOS 8 示例项目 - 需要 iOS 8
- iOS 7 示例项目 - 适用于 iOS 7+
由于 UILabels 中的表视图单元格包含动态内容,这些项目是具有可变行高的表视图的完整工作示例。
Xamarin (C#/.NET)
如果使用的是 Xamarin,请查看 @KentBoogaart 组合的此示例项目。
评论
heightForRowAtIndexPath
cellForRowAtIndexPath
iOS8
iOS9
iOS10
@smileyborg提出的解决方案几乎是完美的。如果您有一个自定义单元格,并且想要一个或多个具有动态高度的单元格,则 systemLayoutSizeFittingSize 方法与启用的 AutoLayout 结合使用将返回 除非您将所有单元格约束从单元格移动到其 contentView(如此处@TomSwift建议的那样,如何调整超级视图的大小以适合所有具有自动布局的子视图?UILabel
CGSizeZero
为此,您需要在自定义 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:firstItem
attribute:cellConstraint.firstAttribute
relatedBy:cellConstraint.relation
toItem:seccondItem
attribute:cellConstraint.secondAttribute
multiplier:cellConstraint.multiplier
constant:cellConstraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
将@smileyborg答案与此混合应该有效。
评论
systemLayoutSizeFittingSize
需要在 contentView 上调用,而不是在 cell 上调用
我刚刚遇到的一个足够重要的问题,可以作为答案发布。
@smileyborg的答案大多是正确的。但是,如果自定义单元格类的方法中有任何代码,例如设置 ,则不会使用此代码运行:layoutSubviews
preferredMaxLayoutWidth
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
这让我困惑了一段时间。然后我意识到这是因为这些只是触发了 上的 layoutSubviews,而不是单元格本身。contentView
我的工作代码如下所示:
TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
请注意,如果您要创建一个新单元格,我很确定您不需要调用,因为它应该已经设置好了。在保存对单元格的引用的情况下,可能应该调用它。无论哪种方式,它都不应该伤害任何东西。setNeedsLayout
另一个提示是,如果您使用单元格子类,您可以在其中设置诸如 .正如@smileyborg所提到的,“您的表格视图单元格的宽度尚未固定为表格视图的宽度”。这是真的,如果你在子类中而不是在视图控制器中工作,那就麻烦了。但是,此时您可以使用表格宽度简单地设置单元格框架: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);
(我碰巧缓存了这个特定的单元格以供重复使用,但这无关紧要)。
以防人们仍然对此有问题。我写了一篇关于将自动布局与 UITableViews 一起使用的快速博客文章:利用动态单元格高度的自动布局以及一个开源组件来帮助使其更抽象且更易于实现。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;
}
更新:您应该使用 iOS 8 中引入的动态调整大小。
评论
tableView:cellForRowAtIndexPath:
tableView:heightForRowAtIndexPath:
systemLayoutSizeFittingSize:
tableView:cellForRowAtIndexPath:
tableView:heightForRowAtIndexPath:
我将 @smileyborg 的 iOS7 解决方案包装在一个类别中
我决定将这个聪明的解决方案@smileyborg到一个类别中。UICollectionViewCell+AutoLayoutDynamicHeightCalculation
该类别还纠正了 @wildmonkey 的回答中概述的问题(从笔尖加载单元格并返回systemLayoutSizeFittingSize:
CGRectZero
)
它不考虑任何缓存,但适合我现在的需求。随意复制、粘贴和破解它。
UICollectionViewCell+AutoLayoutDynamicHeightCalculation.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
UICollectionViewCell+AutoLayoutDynamicHeightCalculation.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:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}];
}
#pragma mark Height
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
collectionViewWidth: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 中做这种爵士乐,但现在就在那里!
评论
[YourCell new]
UICollectionViews
像 @Bob-Spryn 一样,我遇到了一个足够重要的问题,所以我把它作为答案发布。
我为@smileyborg的答案挣扎了一会儿。我遇到的问题是,如果您在 IB 中使用 IB 中的附加元素(、等)定义了原型单元格,当您使用 [ 实例化单元格时,它不会实例化该单元格中的所有其他元素,除非您编写了代码来执行此操作。(我也有类似的经历。UILabels
UIButtons
[YourTableViewCellClass alloc] init]
initWithStyle
要让情节提要实例化所有其他元素,请获取您的单元格(不是因为这会导致有趣的问题。执行此操作时,将在 IB 中定义的所有元素被实例化。[tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"]
[tableView dequeueReusableCellWithIdentifier:forIndexPath:]
解决情节提要自动布局问题的好方法:
- (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;
}
评论
这是我的解决方案:
您需要在加载视图之前告诉它。否则,它将无法按预期运行。TableView
estimatedHeight
Objective-C语言
- (void)viewWillAppear:(BOOL)animated {
_messageField.delegate = self;
_tableView.estimatedRowHeight = 65.0;
_tableView.rowHeight = UITableViewAutomaticDimension;
}
Swift 4.2 更新
override func viewWillAppear(_ animated: Bool) {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 65.0
}
评论
estimatedRowHeight
tableView
estimatedRowHeight
另一个“解决方案”:跳过所有这些挫败感,改用 UIScrollView 来获得外观和感觉与 UITableView 相同的结果。
这对我来说是痛苦的“解决方案”,在总共投入了 20+ 非常令人沮丧的时间试图构建类似 smileyborg 建议的东西并在数月和三个版本的 App Store 版本中失败之后。
我的看法是,如果你真的需要 iOS 7 支持(对我们来说,这是必不可少的),那么这项技术太脆弱了,你会尝试拔掉你的头发。而且,除非您使用一些高级行编辑功能和/或确实需要支持 1000+ 个“行”(在我们的应用程序中,实际上永远不会超过 20 行),否则 UITableView 通常完全是矫枉过正的。
额外的好处是,与UITableView附带的所有委托废话和来回相比,代码变得非常简单。它只是 viewOnLoad 中的一个代码循环,看起来很优雅且易于管理。
以下是一些关于如何做到这一点的提示:
使用 Storyboard 或 nib 文件,创建 ViewController 和关联的根视图。
将 UIScrollView 拖动到根视图上。
将约束 top、bottom、left 和 right 约束添加到顶级视图,以便 UIScrollView 填充整个根视图。
在 UIScrollView 中添加一个 UIView,并将其称为“容器”。将 top、bottom、left 和 right 约束添加到 UIScrollView(其父级)。关键技巧:还要添加一个“相等宽度”约束来链接 UIScrollView 和 UIView。
注意:你将收到错误“滚动视图具有不明确的可滚动内容高度”,并且容器 UIView 的高度应为 0 像素。当应用程序运行时,这两个错误似乎都无关紧要。
为每个“单元”创建笔尖文件和控制器。使用 UIView,而不是 UITableViewCell。
在根 ViewController 中,实质上是将所有“行”添加到容器 UIView,并以编程方式添加约束,将其左右边缘链接到容器视图,将其顶部边缘链接到容器视图顶部(对于第一项)或上一个单元格。然后将最后一个单元格链接到容器底部。
对我们来说,每个“行”都在一个笔尖文件中。因此,代码如下所示:
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!)
}
}
下面是 UITools.addViewToTop 的代码:
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 来弄清楚之前阅读我的帖子。 :)
评论
对于上面的 iOS 8,它非常简单:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 80
self.tableView.rowHeight = UITableView.automaticDimension
}
或
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableView.automaticDimension
}
但对于 iOS 7,关键是计算自动布局后的高度:
func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
cell.setNeedsLayout()
cell.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
return 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 = UITableViewAutomaticDimension
theTable.estimatedRowHeight = 44.0;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
cell2height=cell.contentView.height
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if #available(iOS 8.0, *) {
return UITableViewAutomaticDimension
} else {
return cell2height
}
}
评论
cellForRowAtIndexPath
可变高度 UITableViewCell 的 Swift 示例
针对 Swift 3 进行了更新
William 胡 的 Swift 回答很好,但它帮助我在第一次学习做某事时有一些简单而详细的步骤。下面的例子是我在学习制作具有可变单元格高度的测试项目。我基于这个基本的 Swift UITableView 示例。UITableView
完成的项目应如下所示:
创建新项目
它可以只是一个单一视图应用程序。
添加代码
将新的 Swift 文件添加到您的项目中。将其命名为 MyCustomCell。此类将保存添加到情节提要中单元格的视图的出口。在这个基本示例中,每个单元格中只有一个标签。
import UIKit
class MyCustomCell: UITableViewCell {
@IBOutlet weak var myCellLabel: UILabel!
}
我们稍后将连接此插座。
打开 ViewController.swift 并确保包含以下内容:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// These strings will be the data for the table view cells
let 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 also
let cellReuseIdentifier = "cell"
@IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// delegate and data source
tableView.delegate = self
tableView.dataSource = self
// Along with auto layout, these are the keys for enabling variable cell height
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.animals[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
重要提示:
正是以下两行代码(以及自动布局)使可变单元格高度成为可能:
tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableViewAutomaticDimension
设置情节提要
将表视图添加到视图控制器,并使用自动布局将其固定到四个侧面。然后将“表视图”单元格拖到“表视图”上。在 Prototype 单元格上,拖动一个 Label。使用自动布局将标签固定到“表视图单元格”的内容视图的四个边缘。
重要提示:
- 自动布局与我上面提到的重要的两行代码一起工作。如果您不使用自动布局,它将无法工作。
其他 IB 设置
自定义类名和标识符
选择“表视图单元格”,并将自定义类设置为(我们添加的 Swift 文件中的类名称)。此外,将 Identifier 设置为 (与我们在上面代码中用于 的字符串相同。MyCustomCell
cell
cellReuseIdentifier
标签零线
在标签中设置行数。这意味着多行,并允许标签根据其内容调整自身大小。0
连接插座
- 控制从情节提要中的“表视图”拖动到代码中的变量。
tableView
ViewController
- 对 Prototype 单元格中的 Label 执行相同的操作,以创建类中的变量。
myCellLabel
MyCustomCell
完成
您现在应该能够运行您的项目并获得具有可变高度的单元格。
笔记
- 此示例仅适用于 iOS 8 及更高版本。如果您仍然需要支持iOS 7,那么这对您不起作用。
- 在将来的项目中,您自己的自定义单元格可能会有多个标签。确保所有内容都固定正确,以便自动布局可以确定要使用的正确高度。您可能还必须使用垂直抗压和拥抱。有关详细信息,请参阅此文章。
如果不固定前缘和后缘(左边和右边),可能还需要设置标签,以便它知道何时换行。例如,如果您在上面的项目中向标签添加了“水平居中”约束,而不是固定前缘和后缘,则需要将以下行添加到方法中:
preferredMaxLayoutWidth
tableView:cellForRowAtIndexPath
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
另请参阅
评论
preferredMaxLayoutWidth
contentSize
就我而言,我必须创建一个自定义单元格,其中包含来自服务器的图像,并且可以是任何宽度和高度。和两个具有动态大小(宽度和高度)的UILabel
我在使用 autolayout 和编程方式的回答中实现了相同的目标:
基本上上面@smileyBorg答案有所帮助,但systemLayoutSizeFittingSize从未对我有用,在我的方法中:
1.不使用自动行高计算属性。2.No 使用估计高度 3.No 不需要不必要的 updateConstraints。4.No 使用自动首选最大布局宽度。5.没有使用systemLayoutSizeFittingSize(应该有使用但对我不起作用,我不知道它在内部做什么),而是我的方法-(float)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显示错误/警告)。只需确保在运行时在控制台日志中不会收到任何自动布局错误即可。
事实上,试图在 IB 中始终更新行高是非常烦人的,有时几乎是不可能的(因为分数值)。
为防止恼人的 IB 警告/错误,您可以选择涉及的视图,并在属性中选择Size Inspector
Ambiguity
Verify Position Only
Xcode 8.x / Xcode 9.x 似乎(有时)做事的方式与 Xcode 7.x 不同,但仍然不正确。例如,即使 / 设置为必需 (1000),Interface Builder 也可能会拉伸或剪裁标签以适合单元格(而不是调整单元格高度以适合标签)。在这种情况下,它甚至可能不显示任何自动布局警告或错误。或者有时它完全可以执行上面描述的 Xcode 7.x 所做的工作。compression resistance priority
hugging priority
评论
假设您有一个带有子视图的单元格,并且您希望单元格的高度足够高以包含子视图 + 填充。
1) 将子视图的底部约束设置为等于 cell.contentView 减去所需的填充。不要对 cell 或 cell.contentView 本身设置约束。
2) 将 tableView 的属性或设置为 。rowHeight
tableView:heightForRowAtIndexPath:
UITableViewAutomaticDimension
3) 将 tableView 的属性或设置为高度的最佳猜测。estimatedRowHeight
tableView:estimatedHeightForRowAtIndexPath:
就是这样。
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension
就我而言,填充是因为 sectionHeader 和 sectionFooter 高度,其中故事板允许我将其更改为最小 1。因此,在 viewDidLoad 方法中:
tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
如果您以编程方式进行布局,以下是在 Swift 中使用锚点的 iOS 10 需要考虑的事项。
有三个规则/步骤
第 1 点:在 viewDidLoad 上设置 tableview 的这两个属性,第一个属性告诉 tableview 应该期望其单元格的动态大小,第二个只是让应用程序计算滚动条指示器的大小,因此它有助于提高性能。
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
第 2 点:这很重要,您需要将子视图添加到单元格的 contentView 而不是视图中,并使用其 layoutsmarginguide 将子视图锚定到顶部和底部,这是如何做到这一点的工作示例。
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
评论
要设置行高和估计行高的自动尺寸,请确保以下步骤使自动尺寸对单元格/行高布局有效。
- 分配和实现 tableview dataSource 和委托
- 分配给 rowHeight 和 estimatedRowHeight
UITableViewAutomaticDimension
- 实现 delegate/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;
}
迅速:
@IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
// Swift 4.2 onwards
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
// Swift 4.1 and below
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Swift 4.2 onwards
return UITableView.automaticDimension
// Swift 4.1 and below
return UITableViewAutomaticDimension
}
对于 UITableviewCell 中的标签实例
- 设置行数 = 0(&换行模式 = 截尾)
- 设置相对于其 superview/单元容器的所有约束(顶部、底部、右侧、左侧)。
- 可选:如果希望标签覆盖最小垂直区域,即使没有数据,也可以设置标签的最小高度。
注意:如果您有多个具有动态长度的标签 (UIElements),则应根据其内容大小进行调整:调整要以更高优先级扩展/压缩的标签的“内容拥抱和抗压缩优先级”。
评论
tableView: heightForRow
tableView: heightForRow
tableView: heightForRow
tableView: heightForRow
if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
我只是对 和 的 2 个值进行了一些愚蠢的尝试和错误,只是认为它可能会提供一些调试见解:rowHeight
estimatedRowHeight
如果同时设置它们或仅设置 你将获得所需的行为:estimatedRowHeight
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1
建议您尽最大努力获得正确的估计值,但最终结果并没有什么不同。它只会影响你的表现。
如果您只设置 rowHeight,即只做:
tableView.rowHeight = UITableViewAutomaticDimension
您的最终结果不会如愿以偿:
如果将 设置为 1 或更小,则无论 .estimatedRowHeight
rowHeight
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1
我崩溃了,并显示以下错误消息:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must 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 type
NSException
关于@smileyborg接受的答案,我发现
[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.text
let 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 = rect
return requredSize.height
}
如果你有一个长字符串。例如,没有换行符的。然后你可能会遇到一些问题。
“据称”的修复被接受的答案和其他一些答案提到。你只需要添加
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
我发现苏拉的回答是最完整、最简洁的,因此不会令人困惑。
虽然没有解释为什么需要这些更改。让我们开始吧。
将以下代码拖放到项目中。
import UIKit
class ViewController: UIViewController {
lazy var label : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.backgroundColor = .red
lbl.textColor = .black
return 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 = 0
print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step6: (98.5, 243.5)
label.preferredMaxLayoutWidth = 100
print("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 = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
请注意,我没有添加任何大小限制。我只添加了 centerX、centerY 约束。但标签的尺寸仍然正确,为什么?
因为 contentSize
。
为了更好地处理这个问题,首先保留 step0,然后注释掉步骤 1-6。让我们留下来。观察行为。setupLayout()
然后取消注释 step1,并观察。
然后取消注释 step2 并观察。
执行此操作,直到您取消注释所有 6 个步骤并观察它们的行为。
从这一切中可以得出什么结论?哪些因素可以改变?contenSize
- 文本长度:如果您的文本较长,则 intrinsicContentSize 的宽度将增加
- 换行符:如果添加,则 intrinsicContentSize 的宽度将是所有行的最大宽度。如果一行有 25 个字符,另一行有 2 个字符,另一行有 21 个字符,那么您的宽度将根据 25 个字符计算
\n
- 允许的行数:您必须将 设置为 否则,您将不会有多行。您将调整 intrinsicContentSize 的高度
numberOfLines
0
numberOfLines
进行调整:想象一下,根据你的文本,你的 intrinsicContentSize 的宽度是 ,高度是 ,但你想将宽度限制在标签的容器上,你要做什么?解决方案是将其设置为所需的宽度。为此,请将
preferredMaxLayoutWidth
设置为,然后新的intrinsicContentSize的宽度大致为。高度显然会超过因为你需要更多的线条。 话虽如此,如果你的约束设置正确,那么你根本不需要使用它!有关更多信息,请参阅此答案及其评论。只有当您没有限制宽度/高度的约束时,您才需要使用,例如有人可能会说“除非文本超过 ”,否则不要换行文本。但是,如果您设置了前导/尾随,那么您就可以 100% 确定!长话短说,这里推荐使用它的大多数答案都是错误的!你不需要它。需要它表明您的约束设置不正确,或者您只是没有约束200
100
130
130
100
preferredMaxLayoutWidth
preferredMaxLayoutWidth
numberOfLines
0
字体大小:另请注意,如果增加 fontSize,则 intrinsicContentSize 的高度将增加。我没有在我的代码中显示这一点。你可以自己尝试。
因此,回到您的 tableViewCell 示例:
您需要做的就是:
- 将 设置为
numberOfLines
0
- 将标签正确地限制在边距/边缘
- 无需设置 .
preferredMaxLayoutWidth
swift 4
@IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var tableView: UITableView!
private var context = 1
override 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 observer
deinit {
NotificationCenter.default.removeObserver(self)
}
如果单元格高度是按内容动态的,则应精确地计算出,然后在呈现单元格之前返回高度值。一种简单的方法是在表视图单元代码中定义计数方法,以便控制器在表单元格高度委托方法中调用。如果高度取决于表格或屏幕的宽度,请不要忘记计算实际单元格帧宽度(默认为 320)。即在表单元格高度委托方法中,先使用 cell.frame 校正单元格宽度,然后调用单元格中定义的计数高度方法,得到合适的值并返回。
生成单元格对象的代码可以在另一个方法中定义,以便调用不同的表视图单元格委托方法。
UITableView.automaticDimension
可以通过Interface Builder进行设置:
Xcode > Storyboard > Size Inspector
表视图单元格>行高>自动
评论
(1) 设置
tableView.rowHeight = UITableView.automaticDimension
通常在 viewDidLoad 中
(2) 干脆不包括 .(如果必须包含它,例如在某种子类中,只需返回 。tableView#heightForRowAt
UITableView.automaticDimension
(3) 在故事板的单元格中。只需确保 contentView 的顶部通过约束连接到内容视图的底部即可。
要实现“3”,只需
单元格中从上到下的每个项目都必须连接到其下方的项目。
Top Item 连接到内容视图 Top。底部项目连接到内容视图底部。
每个项目必须具有固定大小(例如,将高度约束设置为“12”),或者必须是 UILabel(或者,在极少数情况下,另一个项目可以为您确定自己的高度,例如堆栈视图)。
这就是整个事情。
评论
contentSize
preferredMaxLayoutWidth
preferredMaxLayoutWidth