如何在表视图的UITableViewCell
中使用自动布局(Auto Layout)以使每个单元格的内容和子视图决定行高(自身/自动),同时保持平滑的滚动性能?
如何在表视图的UITableViewCell
中使用自动布局(Auto Layout)以使每个单元格的内容和子视图决定行高(自身/自动),同时保持平滑的滚动性能?
以下前两步适用于您开发的任何iOS版本。
UITableViewCell
子类中,添加约束条件,使单元格的子视图的边缘固定到单元格的contentView的边缘上(最重要的是顶部和底部边缘)。注意:不要将子视图固定到单元格本身;只能固定到单元格的contentView
!让这些子视图的内在内容大小驱动表格视图单元格的内容视图的高度,确保每个子视图在垂直方向上的内容压缩阻力和内容吸附约束没有被您添加的更高优先级的约束条件覆盖。(什么?点击这里。)self.tableView.rowHeight = UITableView.automaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
contentView
的高度,该高度基于内容视图的已知固定宽度(基于表格视图的宽度,减去任何其他内容,如部分索引或附件视图)和您添加到单元格的内容视图和子视图的自动布局约束。一旦确定了实际单元格高度,旧的行高估计值就会更新为新的实际高度(并根据需要进行表格视图的contentSize/contentOffset调整)。estimatedRowHeight
属性(在 viewDidLoad
或类似方法中)为一个常量值,即"平均"行高。只有当你的行高具有极端的变化(例如相差一到两个数量级),并且你注意到在滚动时滚动指示器会"跳跃"时,你才需要实现 tableView:estimatedHeightForRowAtIndexPath:
来进行最小限度的计算以返回每行更准确的估计值。
tableView:cellForRowAtIndexPath:
方法中不返回到表格视图以实际渲染在屏幕上。)接下来,必须使用与它在表格视图中显示时相同的内容(例如文本、图像等)配置单元格。UITableViewCell
的contentView
上使用systemLayoutSizeFittingSize:
方法,找出所需单元格高度。使用UILayoutFittingCompressedSize
获取适合所有单元格内容的最小大小。然后可以从tableView:heightForRowAtIndexPath:
委托方法返回高度。
tableView:heightForRowAtIndexPath:
(以计算滚动指示器的大小)。estimatedRowHeight
属性。这将为表视图提供一个临时的估计/占位符,用于尚未在屏幕上的单元格的行高。然后,当这些单元格即将滚动到屏幕上时,将计算实际的行高(通过调用tableView:heightForRowAtIndexPath:
),并更新估计高度为实际高度。estimatedRowHeight
属性(在viewDidLoad
或类似方法中)为一个常量值,即“平均”行高度。只有当您的行高具有极端的变化(例如相差一个数量级),并且您注意到滚动指示器在滚动时“跳跃”,您才应该实现tableView:estimatedHeightForRowAtIndexPath:
以执行所需的最小计算,以返回每行更准确的估计值。
tableView:heightForRowAtIndexPath:
中进行约束求解时仍然发现性能不可接受地慢,则不幸的是,您需要为单元格高度实现一些缓存。(这是苹果工程师建议的方法。)总体思路是让自动布局引擎第一次解决约束,然后缓存计算出的该单元格的高度,并将缓存值用于该单元格的所有未来高度请求。当然,诀窍在于确保在发生任何可能导致单元格高度更改的事件(主要是单元格内容更改或其他重要事件发生时,如用户调整动态类型文本大小滑块时)时清除单元格的缓存高度。
- (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;
}
}
这些项目是包含动态内容的 UILabel 的表视图的可变行高度的完整工作示例。
如果您正在使用 Xamarin,请查看由 @KentBoogaart 组成的 示例项目。
iOS8
的实现仍然推荐用于iOS9
和iOS10
吗?还是有新的方法? - koeniOS 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)
完整的示例代码在这里。
更新至Swift 3
William Hu的Swift答案很好,但对于第一次学习某个东西时,有些简单而详细的步骤会对我有所帮助。下面的示例是我在学习如何制作具有可变单元格高度的UITableView
时创建的测试项目。我基于这个Swift的基本UITableView示例进行了修改。
最终项目应如下所示:
可以只是一个Single View Application。
向项目中添加一个新的Swift文件。将其命名为MyCustomCell。此类将保存你在故事板中为单元格添加的视图的输出口。在这个基本示例中,我们每个单元格只有一个标签。
import UIKit
class MyCustomCell: UITableViewCell {
@IBOutlet weak var myCellLabel: UILabel!
}
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).")
}
}
重要提示:
It is the following two lines of code (along with auto layout) that make the variable cell height possible:
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
在视图控制器中添加一个表格视图(Table View),并使用自动布局将其固定在四个边缘。然后将表格视图单元格(Table View Cell)拖到表格视图上。在原型单元格(Prototype cell)上,拖动一个标签(Label)。使用自动布局将标签固定在表格视图单元格的内容视图(content view)的四个边缘。
重要提示:
自定义类名和标识符
选择表格视图单元格(Table View Cell),将自定义类设置为MyCustomCell
(我们添加的Swift文件中的类名)。同时将标识符(Identifier)设置为cell
(与上面代码中使用的cellReuseIdentifier
相同的字符串)。
标签零行
在标签中将行数设置为0
。这意味着多行,并允许标签根据其内容自动调整大小。
连接插座(Outlets)
ViewController
代码中的tableView
变量进行控制拖动。 MyCustomCell
类中的myCellLabel
变量上。现在您应该能够运行项目并获得可变高度的单元格。
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
preferredMaxLayoutWidth
设定为单元格的 contentSize
不是更好吗?这样,如果你有一个 accessoryView 或者它被滑动编辑,那么它仍然会被考虑在内。 - mfaani我决定将@smileyborg的聪明解决方案包装到UICollectionViewCell+AutoLayoutDynamicHeightCalculation
类别中。
该类别还纠正了@wildmonkey答案中概述的问题(从nib加载单元格和systemLayoutSizeFittingSize:
返回CGRectZero
)。
它不考虑任何缓存,但目前符合我的需求。随意复制、粘贴并对其进行修改。
#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
#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]
作为虚拟对象。只要约束代码构建代码在你的实例中被触发,并且你以编程方式触发布局传递,你就可以开始了。 - Adam WaiteUICollectionViews
。 - Ricardo Sanchez-Saez这是我的解决方案:
在加载视图之前,您需要告诉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
中使用的高度应该使用最小值还是最大值? - János@smileyborg提出的解决方案几乎完美。如果您有自定义单元格,并且想要一个或多个具有动态高度的UILabel
,则启用AutoLayout的systemLayoutSizeFittingSize方法将返回CGSizeZero
,除非您将所有单元格约束从单元格移动到其contentView中(正如@TomSwift在此处建议的那样:How to resize superview to fit all subviews with autolayout?)。
为此,您需要在自定义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。 - onmyway133我遇到了一个很重要的问题,需要作为答案发布。
@smileyborg的答案大部分是正确的。然而,如果你在自定义单元格类的layoutSubviews
方法中有任何代码,比如设置preferredMaxLayoutWidth
,那么这段代码将不会运行:
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
有一段时间让我感到困惑。后来我意识到,这是因为它们只在contentView
上触发了layoutSubviews
,而没有在单元格本身上触发。
我的工作代码看起来像这样:
TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
请注意,如果您正在创建新的单元格,我相信您不需要调用setNeedsLayout
,因为它应该已经设置好了。在保存对单元格的引用时,您应该调用它。无论哪种方式,都不应该有什么问题。
如果您正在使用单元格子类,并设置了像preferredMaxLayoutWidth
这样的内容,请注意以下提示。正如@smileyborg所提到的,“您的表视图单元格尚未具有固定到表视图宽度的宽度”。如果您在子类中进行工作而不在视图控制器中进行,则会遇到麻烦。但是您可以使用表格宽度简单地设置单元格框架:
例如,在计算高度时:
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);
(我碰巧缓存了这个特定的单元格以便重用,但这与问题无关。)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}
更新:您应该使用iOS 8中引入的动态调整大小。
tableView:heightForRowAtIndexPath:
中的tableView:cellForRowAtIndexPath:
是否可以了? - AntstableView:cellForRowAtIndexPath:
中调用systemLayoutSizeFittingSize:
并缓存结果,然后在tableView:heightForRowAtIndexPath:
中使用它,只要约束正确设置就可以正常工作! - Ants(适用于 Xcode 8.x / Xcode 9.x 请参考底部内容)
注意下面在 Xcode 7.x 中可能会导致混淆的问题:
Interface Builder 无法正确处理自动调整大小的单元格设置。即使您的约束条件是绝对有效的,IB仍然会报错并给出令人困惑的建议和错误提示。原因是IB不愿意根据您的约束条件改变行高(以使单元格适应您的内容),而是保持行高不变,并开始建议您更改约束条件,这些您应该忽略。
例如,假设您已经设置好了一切,没有警告,没有错误,一切正常工作。
现在如果您更改字体大小(在此示例中,我将说明标签字体大小从17.0更改为18.0)。
由于字体大小增加,标签现在希望占用3行(之前它占用2行)。
如果Interface Builder按照预期工作,它将调整单元格的高度以适应新标签的高度。但是实际发生的情况是IB显示红色自动布局错误图标,并建议您修改抱紧/压缩优先级。
您应该忽略这些警告。而是可以手动更改行高(选择Cell > 大小检查器 > 行高)。
(对于Xcode 8.x / Xcode 9.x,请参考以下内容)
在 Xcode 8.x / Xcode 9.x 中,Interface Builder能够正确处理自动调整大小的单元格设置,因此不需要手动更改行高。如果您遇到与自动调整大小有关的问题,请确保使用最新版本的Xcode。
我通过点击上下步进器,逐一更改这个高度值直到红色箭头错误消失!(实际上你会得到黄色警告,这时只需执行“更新框架”就可以了,它应该能正常工作)。
* 请注意,您实际上不必解决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也可能拉伸或裁剪标签以适应单元格(而不是调整单元格高度以适应标签)。在这种情况下,它甚至可能不显示任何AutoLayout警告或错误。或者有时会完全按照上述Xcode 7.x所做的方式执行。
UITableViewAutomaticDimension
分配给rowHeight和estimatedRowHeightheightForRowAt
),并返回一个值UITableViewAutomaticDimension
-
Objective 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 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
}
注意:如果您有多个具有动态长度的标签(UIElements),这些标签应根据其内容大小进行调整:为您希望具有更高优先级扩展/压缩的标签调整“内容吸附和压缩阻力优先级”。
tableView: heightForRow
数据源,但它对我起作用。 - HemangtableView: heightForRow
的情况下正常工作。(对于iOS 10-,需要tableView: heightForRow
) - KrunaltableView: heightForRow
中... if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
- Krunal
contentSize
和preferredMaxLayoutWidth
的工作原理。请参见此处。话虽如此,如果您正确设置约束,则不需要preferredMaxLayoutWidth
,事实上它可能会产生意外结果。 - mfaani