如何在表格视图中使用UITableViewCell
内的自动布局让每个单元格的内容和子视图确定行高(本身/自动),同时保持平滑的滚动性能?
答案 0 :(得分:2337)
TL; DR:不喜欢读书?直接跳转到GitHub上的示例项目:
无论您正在开发哪种iOS版本,以下前两个步骤均适用。
在UITableViewCell
子类中,添加约束,以便单元格的子视图的边缘固定在单元格的边缘 contentView (最重要的是顶部AND底边)。 注意:不要将子视图固定到单元格本身;仅限于单元格contentView
!通过确保内容压缩阻力,让这些子视图的内在内容大小驱动表格视图单元格的内容视图的高度每个子视图的垂直维度中的和内容拥抱约束不会被您添加的更高优先级约束覆盖。 (Huh? Click here.)
请记住,我们的想法是将单元格的子视图垂直连接到单元格的内容视图,这样他们就可以施加压力"并使内容视图展开以适合它们。使用包含一些子视图的示例单元格,可以直观地看到您的约束的某些 (并非所有!)需要看起来像:
您可以想象,随着更多文本被添加到上面示例单元格中的多线体标签,它将需要垂直增长以适合文本,这将有效地迫使细胞在高度上增长。 (当然,您需要正确的约束才能使其正常工作!)
确保您的约束正确是使用自动布局获得动态单元格高度的最难也是最重要的部分。如果你在这里犯了错误,它可能会阻止其他一切工作 - 所以慢慢来!我建议在代码中设置约束,因为您确切地知道在哪里添加了哪些约束,并且在出现问题时更容易调试。在代码中添加约束可以比使用布局锚点的Interface Builder或GitHub上提供的一个非常棒的开源API一样简单并且功能强大得多。
updateConstraints
方法中执行此操作一次。请注意,updateConstraints
可能会被多次调用,因此为了避免多次添加相同的约束,请确保在updateConstraints
中包含约束添加代码以检查布尔属性,例如{ {1}}(在运行约束添加代码一次后设置为YES)。另一方面,如果您有更新现有约束的代码(例如在某些约束上调整didSetupConstraints
属性),请将其放在constant
中,但在检查updateConstraints
之外,以便它每次调用方法时都可以运行。对于单元中每个唯一的约束集,请使用唯一的单元重用标识符。换句话说,如果您的单元格具有多个唯一布局,则每个唯一布局应接收其自己的重用标识符。 (当您的单元格变体具有不同数量的子视图,或者子视图以不同的方式排列时,您需要使用新的重用标识符的良好提示。)
例如,如果您在每个单元格中显示电子邮件,则可能有4种独特的布局:仅包含主题的邮件,包含主题和正文的邮件,包含主题和照片附件的邮件以及包含主题的邮件主题,身体和照片附件。每个布局都有完全不同的约束来实现它,因此一旦初始化单元并为这些单元类型之一添加约束,单元应该获得特定于该单元类型的唯一重用标识符。这意味着当您将单元格出列以便重复使用时,已经添加了约束并准备好使用该单元格类型。
请注意,由于内在内容大小的差异,具有相同约束(类型)的单元格可能仍然具有不同的高度!由于内容的大小不同,不要将基本不同的布局(不同的约束)与不同的计算视图帧(由相同的约束条件解决)混淆。
要启用自行调整表格视图单元格,必须设置表格视图 rowHeight属性为UITableViewAutomaticDimension。你也必须 为estimatedRowHeight属性赋值。一旦两个 设置这些属性后,系统使用自动布局来计算 行的实际高度
对于iOS 8,Apple已经内置了以前必须在iOS 8之前实现的大部分工作。为了使自定义单元机制能够工作,您必须首先设置didSetupConstraints
表上的属性查看常量rowHeight
。然后,您只需通过将表视图的UITableViewAutomaticDimension
属性设置为非零值来启用行高估计,例如:
estimatedRowHeight
这样做是为表视图提供临时估计/占位符,用于尚未在屏幕上显示的单元格的行高。然后,当这些单元格即将在屏幕上滚动时,将计算实际行高。为了确定每一行的实际高度,表格视图会根据内容视图的已知固定宽度(基于表格视图的宽度)自动询问每个单元格self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
需要的高度。 ,减去任何其他内容,如部分索引或附件视图)以及您添加到单元格内容视图和子视图中的自动布局约束。确定此实际单元格高度后,将使用新的实际高度更新行的旧估计高度(并且根据需要对表格视图的contentSize / contentOffset进行任何调整)。
一般来说,您提供的估算值不必非常准确 - 它仅用于在表格视图中正确调整滚动指示器的大小,并且表格视图可以很好地调整滚动指示器在屏幕上滚动单元格时进行错误估计。您应该将表格视图(contentView
或类似)中的estimatedRowHeight
属性设置为常数值,即"平均值"行高。 仅当您的行高具有极端可变性(例如,相差一个数量级)并且您注意到滚动指示符"跳跃"当您滚动时,您需要执行viewDidLoad
来执行所需的最小计算,以便为每行返回更准确的估算值。
首先,实例化表视图单元的屏幕外实例,每个重用标识符的一个实例,严格用于高度计算。 (屏幕外意味着单元格引用存储在视图控制器上的属性/ ivar中,并且永远不会从tableView:estimatedHeightForRowAtIndexPath:
返回以使表格视图实际呈现在屏幕上。)接下来,必须使用确切内容配置单元格(例如,文本,图像等)如果要在表格视图中显示它将保留。
然后,强制单元格立即布置其子视图,然后使用tableView:cellForRowAtIndexPath:
systemLayoutSizeFittingSize:
上的UITableViewCell
方法找出所需的高度细胞是。使用contentView
获取适合单元格所有内容所需的最小大小。然后可以从UILayoutFittingCompressedSize
委托方法返回高度。
如果你的表视图中有超过几十行,你会发现在第一次加载表视图时,执行自动布局约束求解会很快陷入主线程,因为tableView:heightForRowAtIndexPath:
被调用第一次加载时的每一行(为了计算滚动指示器的大小)。
从iOS 7开始,您可以(并且绝对应该)在表格视图中使用tableView:heightForRowAtIndexPath:
属性。这样做是为表视图提供临时估计/占位符,用于尚未在屏幕上显示的单元格的行高。然后,当这些单元格即将在屏幕上滚动时,将计算实际行高(通过调用estimatedRowHeight
),并使用实际行更新估计的高度。
一般来说,您提供的估算值不必非常准确 - 它仅用于在表格视图中正确调整滚动指示器的大小,并且表格视图可以很好地调整滚动指示器在屏幕上滚动单元格时进行错误估计。您应该将表格视图(tableView:heightForRowAtIndexPath:
或类似)中的estimatedRowHeight
属性设置为常数值,即"平均值"行高。 仅当您的行高具有极端可变性(例如,相差一个数量级)并且您注意到滚动指示符"跳跃"当您滚动时,您需要执行viewDidLoad
来执行所需的最小计算,以便为每行返回更准确的估算值。
如果你已经完成了以上所有工作并且在tableView:estimatedHeightForRowAtIndexPath:
中进行约束求解时仍然发现性能慢得令人无法接受,那么很遗憾,你需要为单元格高度实现一些缓存。 (这是Apple工程师建议的方法。)一般的想法是让Auto Layout引擎第一次解决约束,然后缓存该单元的计算高度,并将缓存值用于将来的所有请求那个细胞的高度。当然,诀窍是确保在任何可能导致单元格高度发生变化的情况下清除单元格的缓存高度 - 主要是当单元格内容发生变化时或其他情况发生变化时发生重要事件(例如用户调整动态类型文本大小滑块)。
tableView:heightForRowAtIndexPath:
这些项目是具有可变行高的表视图的完整工作示例,因为表视图单元格包含UILabels中的动态内容。
如果您正在使用Xamarin,请查看由sample project汇总的@KentBoogaart。
答案 1 :(得分:154)
对于IOS8,它非常简单:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 80
self.tableView.rowHeight = UITableViewAutomaticDimension
}
OR
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
但是对于IOS7,关键是计算自动布局后的高度,
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)
完整示例代码为here。
编辑 Swift 4.2
UITableViewAutomaticDimension
改为
UITableView.automaticDimension
答案 2 :(得分:87)
更新了Swift 3
William Hu的Swift答案很好,但是当我第一次学习做某事时,它帮助我做了一些简单而详细的步骤。下面的示例是我的测试项目,同时学习制作具有可变单元格高度的UITableView
。我的基础是this basic UITableView example for Swift。
完成的项目应如下所示:
它可以只是一个单一视图应用程序。
将新的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。使用自动布局将标签固定到表视图单元格的内容视图的四个边缘。
重要提示:
自定义类名称和标识符
选择Table View Cell并将自定义类设置为MyCustomCell
(我们添加的Swift文件中的类名)。同时将标识符设置为cell
(与上面代码中cellReuseIdentifier
使用的字符串相同。
标签零行
将标签中的行数设置为0
。这意味着多行,并允许标签根据其内容调整自身大小。
连接奥特莱斯
tableView
代码中的ViewController
变量。 myCellLabel
类中的MyCustomCell
变量相同的操作。您现在应该能够运行项目并获得具有可变高度的单元格。
如果您没有固定前沿(左侧和右侧)边缘,您可能还需要设置标签preferredMaxLayoutWidth
,以便它知道何时换行。例如,如果您在上面的项目中为标签添加了一个中心水平约束而不是固定前缘和后缘,那么您需要将此行添加到tableView:cellForRowAtIndexPath
方法中:
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
答案 3 :(得分:65)
我决定将@smileyborg的这个聪明的解决方案包装到UICollectionViewCell+AutoLayoutDynamicHeightCalculation
类别中。
此类别还可以解决@ wildmonkey的答案(从笔尖加载单元格并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中做这个爵士乐,但现在就是这样!
答案 4 :(得分:52)
这是我的解决方案:
在加载视图之前,您需要告诉TableView
estimatedHeight
。否则它将无法像预期的那样表现。
<强>目标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
}
答案 5 :(得分:46)
@smileyborg提出的解决方案几乎是完美的。如果您有一个自定义单元格,并且您希望一个或多个UILabel
具有动态高度,则 systemLayoutSizeFittingSize 方法与启用的AutoLayout结合使用会返回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答案与此混合应该有效。
答案 6 :(得分:25)
一个重要的问题,我刚刚发布作为答案发布。
@ 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);
(我碰巧将这个特定的单元格缓存以供重复使用,但这无关紧要)。
答案 7 :(得分:22)
如果人们仍然遇到这个问题。我写了一篇关于将Autolayout与UITableViews Leveraging Autolayout For Dynamic Cell Heights一起使用的快速博客文章以及一个开源组件,以帮助使其更加抽象和易于实现。 https://github.com/Raizlabs/RZCellSizeManager
答案 8 :(得分:19)
只要您的单元格中的布局良好。
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}
更新:您应该使用iOS 8中引入的动态调整大小。
答案 9 :(得分:19)
(对于Xcode 8.x / Xcode 9.x在底部读取)
请注意Xcode 7.x中的以下问题,这可能会引起混淆:
Interface Builder无法正确处理自动调整大小的单元格设置。即使您的约束绝对有效,IB仍会抱怨并给您一些令人困惑的建议和错误。原因是IB不愿意根据您的约束条件更改行的高度(以便单元格适合您的内容)。相反,它会保持行的高度不变,并开始建议您更改约束, 您应该忽略 。
例如,假设你已经设置好一切,没有警告,没有错误,一切正常。
现在,如果您更改字体大小(在此示例中,我将描述标签字体大小从17.0更改为18.0)。
由于字体大小增加,标签现在要占用3行(之前它占据2行)。
如果Interface Builder按预期工作,它将调整单元格的高度以适应新的标签高度。然而,实际发生的是IB显示红色自动布局错误图标并建议您修改拥抱/压缩优先级。
您应该忽略这些警告。您可以*做的是手动更改行的高度(选择单元格&gt;大小检查器&gt;行高)。
我一次只更改此高度一次(使用向上/向下步进),直到红色箭头错误消失! (你实际上会得到黄色警告,此时只需要继续“更新帧”,它应该全部工作)。
*请注意,您实际上不必在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
设置为required(1000),Interface Builder也可以拉伸或剪裁标签以适合单元格(而不是调整单元格高度以适应标签周围)。在这种情况下,它甚至可能不会显示任何AutoLayout警告或错误。或者有时它完全与Xcode 7.x所做的完全相同,如上所述。
答案 10 :(得分:18)
设置行高的自动尺寸&amp;估计行高,确保执行以下步骤,自动尺寸对单元格/行高度布局有效。
UITableViewAutomaticDimension
分配给rowHeight&amp; estimatedRowHeight 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中的标签实例
注意:如果您有多个动态长度的标签(UIElements),应根据其内容大小进行调整:调整您的标签'内容拥抱和压缩阻力优先级'想要以更高的优先级扩展/压缩。
答案 11 :(得分:16)
与@Bob-Spryn一样,我遇到了一个非常重要的问题,我将其作为答案发布。
我在@smileyborg's回答了一段时间。我遇到的问题是,当您使用[UILabels
实例化单元格时,您在IB中使用其他元素(UIButtons
,[YourTableViewCellClass alloc] init]
等)定义了原型单元格。它不会实例化该单元格中的所有其他元素,除非您已编写代码来执行此操作。 (我与initWithStyle
有类似的经历。)
要让故事板实例化所有其他元素,请使用[tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"]
获取您的单元格(不[tableView dequeueReusableCellWithIdentifier:forIndexPath:]
,因为这会导致有趣的问题。)当您执行此操作时,您在IB中定义的所有元素都将是实例
答案 12 :(得分:15)
Dynamic Table View Cell Height and 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;
}
答案 13 :(得分:13)
另一个&#34;解决方案&#34;:跳过所有这些挫折并使用UIScrollView代替获得与UITableView相同的结果。
这是痛苦的解决方案&#34;对我来说,在完成了20多个非常令人沮丧的小时之后,他们试图构建像smileyborg建议的东西,并且已经失败了几个月和三个版本的App Store版本。
我的看法是,如果你真的需要iOS 7支持(对我们而言,这是必不可少的),那么这项技术太脆弱了,你会把你的头发拉出来试试。除非您使用某些高级行编辑功能和/或确实需要支持1000+&#34;行,否则UITableView通常会完全过度杀伤。 (在我们的应用程序中,它实际上从不超过20行)。
额外的好处是,与UITableView附带的所有委托垃圾和来回相比,代码变得非常简单。它只是viewOnLoad中的一个代码循环,看起来很优雅,易于管理。
以下是有关如何操作的一些提示:
1)使用Storyboard或nib文件,创建一个ViewController和关联的根视图。
2)将UIScrollView拖到根视图上。
3)向顶级视图添加约束top,bottom,left和right约束,以便UIScrollView填充整个根视图。
4)在UIScrollView中添加一个UIView并将其命名为#34; container&#34;。将顶部,底部,左侧和右侧约束添加到UIScrollView(其父级)。 KEY TRICK:同时添加&#34; Equal widths&#34;用于链接UIScrollView和UIView的约束。
您将收到错误&#34;滚动视图具有不明确的可滚动内容高度&#34;并且您的容器UIView应该具有0像素的高度。当应用程序运行时,这两个错误似乎都不重要。
5)为每个&#34;单元格创建nib文件和控制器&#34;。使用UIView而不是UITableViewCell。
5)在你的根ViewController中,你基本上添加了所有&#34;行&#34;到容器UIView并以编程方式添加将其左右边缘链接到容器视图的约束,它们的顶边到容器视图顶部(对于第一个项目)或前一个单元格。然后将最终单元格链接到容器底部。
对我们来说,每一行&#34;行&#34;是在一个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!)
}
}
这是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))
}
}
唯一的&#34;陷阱&#34;到目前为止,我已经找到了这种方法,UITableView有一个很好的特点,即#34;浮动&#34;滚动时视图顶部的节标题。除非你添加更多编程,否则上述解决方案不会这样做,但对于我们的特殊情况,这个功能并非100%必不可少,并且当它消失时没有人注意到。
如果你想要在你的单元格之间划分,只需在自定义&#34;单元格底部添加1像素高的UIView&#34;看起来像一个分隔符。
一定要打开&#34;弹跳&#34;并且&#34;垂直弹跳&#34;使刷新控制工作,所以它看起来更像是一个tableview。
TableView会在您的内容下显示一些空行和分隔符,如果它没有填满整个屏幕,因为此解决方案没有。但就个人而言,我更喜欢那些空行无论如何 - 在可变的单元高度下,它总是看起来像马车&#34;无论如何,对我来说,那里有空行。
希望其他程序员在阅读我的帖子之前浪费20多个小时试图在他们自己的应用程序中找到Table View。 :)
答案 14 :(得分:12)
答案 15 :(得分:11)
I had to use dynamic views (setup views and constraints by code) and when I wanted to set preferredMaxLayoutWidth label's width was 0. So I've got wrong cell height.
Then I added
[cell layoutSubviews];
before executing
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
After that label's width was as expected and dynamic height was calculating right.
答案 16 :(得分:8)
假设您有一个带子视图的单元格,并且您希望单元格的高度足够高以包含子视图+填充。
1)将子视图的底部约束设置为等于cell.contentView减去所需的填充。不要在单元格或cell.contentView本身上设置约束。
2)将tableView&#39; rowHeight
属性或tableView:heightForRowAtIndexPath:
设置为UITableViewAutomaticDimension
。
3)将tableView&#39; estimatedRowHeight
属性或tableView:estimatedHeightForRowAtIndexPath:
设置为最佳高度猜测。
那就是它。
答案 17 :(得分:4)
如果你以编程方式进行布局,这里是使用Swift中的锚点考虑iOS 10的内容。
有三个规则/步骤
NUMBER 1:在viewDidLoad上设置tableview的这两个属性,第一个告诉tableview应该期望其单元格上的动态大小,第二个是让应用程序计算滚动条指示符的大小,所以它有助于提高绩效。
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
NUMBER 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 {
}
如果你这样做,你将覆盖你的实施。
对于tableviews中的动态单元格遵循以下3条规则。
这是一个有效的实施方案 https://github.com/jamesrochabrun/MinimalViewController
答案 18 :(得分:3)
如果您有 long 字符串。例如没有的一个换行符。然后,您可能会遇到一些问题。
已接受的答案和其他一些答案提到了此修复程序。您只需要添加
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
我发现Suragh's answer最完整,最简洁,因此不会造成混淆。
尽管没有解释为什么。来做吧。
将以下代码拖放到项目中。
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
}
}
请注意,我没有添加任何 size 约束。我只添加了centerX,centerY约束。但是仍然可以正确调整标签尺寸,为什么?
由于contentSize
。
要更好地处理此问题,请首先保留step0,然后注释掉步骤1-6。让setupLayout()
停留。观察行为。
然后取消注释步骤1,并观察。
然后取消注释步骤2并观察。
执行此操作,直到取消注释所有6个步骤并观察其行为为止。
contenSize
?\n
,那么nativeContentSize的宽度将是所有行的最大宽度。如果一行包含25个字符,另一行包含2个字符,另一行包含21个字符,则您的宽度将基于25个字符计算numberOfLines
设置为0
,否则您将没有多行。您的numberOfLines
将调整您的internalContentSize的 height 进行调整::假设根据您的文本,您的internalContentSize的宽度为200
,高度为100
,但是您想将宽度限制为标签的容器该怎么办?解决方案是将其设置为所需的宽度。您可以通过将preferredMaxLayoutWidth
设置为130
来做到这一点,然后新的internalContentSize的宽度大约为130
。高度显然会大于100
,因为您需要更多的行。
话虽这么说,如果您的约束设置正确,那么您根本就不需要使用它!有关更多信息,请参见this answer及其注释。仅当您没有限制宽度/高度的约束时,才需要使用preferredMaxLayoutWidth
,因为您可能会说“除非超过preferredMaxLayoutWidth
,否则不要包装文本”。但是如果您将开头/结尾设置为numberOfLines
并将0
设置为numberOfLines
,那么100%的把握就可以了! 长话短说,这里推荐使用它的大多数答案是错误的!不用了需要它的迹象表明您的约束设置不正确或没有约束
字体大小:另外请注意,如果您增加fontSize,那么nativeContentSize的 height 将会增加。我没有在代码中显示。您可以自己尝试。
所以回到您的tableViewCell示例:
您需要做的是:
0
设置为preferredMaxLayoutWidth
答案 19 :(得分:1)
在我的情况下,我必须创建一个自定义单元格,其中的图像来自服务器,可以是任何宽度和高度。还有两个动态尺寸(宽度和高度)的UILabel
我在回答autolayout并以编程方式实现了相同的目标:
基本上@smileyBorg回答帮助了但是systemLayoutSizeFittingSize从来没有对我有用,在我的方法中:
1。不使用自动行高计算属性。 2.没有使用估计的高度3.不需要不必要的updateConstraints。 4.不使用自动首选最大布局宽度。 5.不使用 systemLayoutSizeFittingSize (应该使用但不能为我工作,我不知道它在内部做什么),而是我的方法 - (浮动)getViewHeight工作,我知道它是什么&#39 ; s在内部做。
答案 20 :(得分:1)
关于@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:是tableview的宽度
答案 21 :(得分:1)
我只是对rowHeight
和estimatedRowHeight
的2个值进行了一些愚蠢的尝试和错误,并且认为它可能会提供一些调试见解:
如果您同时设置或只设置estimatedRowHeight
,您将获得所需的行为:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1
建议您尽力获得正确的估算,但最终结果并无不同。这只会影响你的表现。
如果你只设置rowHeight即只执行:
tableView.rowHeight = UITableViewAutomaticDimension
您的最终结果不符合要求:
如果您将estimatedRowHeight
设置为1或更小,那么无论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
答案 22 :(得分:1)
在我的情况下,填充是因为sectionHeader和sectionFooter高度,故事板允许我将其更改为最小1.因此在viewDidLoad方法中:
tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
答案 23 :(得分:0)
只需在viewcontroller中添加这两个函数,它就可以解决您的问题。这里,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
}
答案 24 :(得分:0)
答案 25 :(得分:-1)
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)
}
答案 26 :(得分:-1)
如果单元格的高度根据内容是动态的,则应精确地将其计算出,然后在渲染单元格之前返回高度值。一种简单的方法是在表格视图单元格代码中定义计数方法,以供控制器在表格单元格高度委托方法上调用。如果高度取决于表格或屏幕的宽度,请不要忘记计算实际单元格框架宽度(默认为320)。也就是说,在表格单元格高度委托方法中,先使用cell.frame校正单元格宽度,然后调用单元格中定义的计数高度方法以获取合适的值并返回。
PS。可以在另一种方法中定义用于生成单元对象的代码,以供不同的表视图单元委托方法调用。
答案 27 :(得分:-4)
另一个在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
}
}