ios 在 UITableView 中使用自动布局进行动态单元格布局和可变行高
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18746929/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
提问by smileyborg
How do you use Auto Layout within UITableViewCell
s in a table view to let each cell's content and subviews determine the row height (itself/automatically), while maintaining smooth scrolling performance?
如何UITableViewCell
在表格视图中使用s内的Auto Layout让每个单元格的内容和子视图确定行高(本身/自动),同时保持平滑滚动性能?
回答by smileyborg
TL;DR:Don't like reading? Jump straight to the sample projects on GitHub:
TL;DR:不喜欢阅读?直接跳转到 GitHub 上的示例项目:
- iOS 8 Sample Project- Requires iOS 8
- iOS 7 Sample Project- Works on iOS 7+
- iOS 8 示例项目- 需要 iOS 8
- iOS 7 示例项目- 适用于 iOS 7+
Conceptual Description
概念描述
The first 2 steps below are applicable regardless of which iOS versions you are developing for.
无论您为哪个 iOS 版本开发,下面的前 2 个步骤都适用。
1. Set Up & Add Constraints
1. 设置和添加约束
In your UITableViewCell
subclass, add constraints so that the subviews of the cell have their edges pinned to the edges of the cell's contentView(most importantly to the top AND bottom edges). NOTE: don't pin subviews to the cell itself; only to the cell's contentView
!Let the intrinsic content size of these subviews drive the height of the table view cell's content view by making sure the content compression resistanceand content huggingconstraints in the vertical dimension for each subview are not being overridden by higher-priority constraints you have added. (Huh? Click here.)
在你的UITableViewCell
子类,添加约束,使细胞的子视图具有其边缘钉在小区的边缘内容查看(最重要的顶部和底部边缘)。注意:不要将子视图固定到单元格本身;只到单元格的contentView
!通过确保每个子视图的垂直维度的内容压缩阻力和内容拥抱约束不会被您添加的更高优先级约束覆盖,让这些子视图的内在内容大小驱动表格视图单元格内容视图的高度。(嗯?点这里。)
Remember, the idea is to have the cell's subviews connected vertically to the cell's content view so that they can "exert pressure" and make the content view expand to fit them. Using an example cell with a few subviews, here is a visual illustration of what some(not all!)of your constraints would need to look like:
请记住,这个想法是让单元格的子视图垂直连接到单元格的内容视图,以便它们可以“施加压力”并使内容视图扩展以适应它们。使用带有几个子视图的示例单元格,以下是一些(不是全部!)约束条件的可视化说明:
You can imagine that as more text is added to the multi-line body label in the example cell above, it will need to grow vertically to fit the text, which will effectively force the cell to grow in height. (Of course, you need to get the constraints right in order for this to work correctly!)
您可以想象,随着更多文本添加到上面示例单元格中的多行正文标签,它需要垂直增长以适应文本,这将有效地迫使单元格的高度增长。(当然,您需要正确设置约束才能使其正常工作!)
Getting your constraints right is definitely the hardest and most important partof getting dynamic cell heights working with Auto Layout. If you make a mistake here, it could prevent everything else from working -- so take your time! I recommend setting up your constraints in code because you know exactly which constraints are being added where, and it's a lot easier to debug when things go wrong. Adding constraints in code can be just as easy as and significantly more powerful than Interface Builder using layout anchors, or one of the fantastic open source APIs available on GitHub.
获得正确的约束绝对是使用自动布局获得动态单元格高度的最困难和最重要的部分。如果你在这里犯了一个错误,它可能会阻止其他一切工作——所以慢慢来!我建议在代码中设置你的约束,因为你确切地知道在何处添加了哪些约束,并且在出现问题时更容易调试。在代码中添加约束与使用布局锚点的 Interface Builder 或 GitHub 上可用的出色开源 API 之一一样简单,而且功能明显更强大。
- If you're adding constraints in code, you should do this once from within the
updateConstraints
method of your UITableViewCell subclass. Note thatupdateConstraints
may be called more than once, so to avoid adding the same constraints more than once, make sure to wrap your constraint-adding code withinupdateConstraints
in a check for a boolean property such asdidSetupConstraints
(which you set to YES after you run your constraint-adding code once). On the other hand, if you have code that updates existing constraints (such as adjusting theconstant
property on some constraints), place this inupdateConstraints
but outside of the check fordidSetupConstraints
so it can run every time the method is called.
- 如果你在代码中添加约束,你应该在
updateConstraints
UITableViewCell 子类的方法中执行一次。请注意,updateConstraints
可能会被多次调用,因此为了避免多次添加相同的约束,请确保将添加约束的代码包装updateConstraints
在检查布尔属性中,例如didSetupConstraints
(在运行约束后将其设置为 YES - 添加代码一次)。另一方面,如果您有更新现有约束的代码(例如调整constant
某些约束上的属性),请将其放置在updateConstraints
检查之外,didSetupConstraints
以便在每次调用方法时都可以运行。
2. Determine Unique Table View Cell Reuse Identifiers
2. 确定唯一的表视图单元重用标识符
For every unique set of constraints in the cell, use a unique cell reuse identifier. In other words, if your cells have more than one unique layout, each unique layout should receive its own reuse identifier. (A good hint that you need to use a new reuse identifier is when your cell variant has a different number of subviews, or the subviews are arranged in a distinct fashion.)
对于单元中每组唯一的约束,使用唯一的单元重用标识符。换句话说,如果您的单元格有多个独特的布局,则每个独特的布局都应该收到自己的重用标识符。(当您的单元格变体具有不同数量的子视图,或者子视图以不同的方式排列时,您需要使用新的重用标识符的一个很好的提示。)
For example, if you were displaying an email message in each cell, you might have 4 unique layouts: messages with just a subject, messages with a subject and a body, messages with a subject and a photo attachment, and messages with a subject, body, and photo attachment. Each layout has completely different constraints required to achieve it, so once the cell is initialized and the constraints are added for one of these cell types, the cell should get a unique reuse identifier specific to that cell type. This means when you dequeue a cell for reuse, the constraints have already been added and are ready to go for that cell type.
例如,如果您在每个单元格中显示一封电子邮件,您可能有 4 种独特的布局:只有一个主题的邮件、一个主题和一个正文的邮件、一个主题和一个照片附件的邮件以及一个主题的邮件,身体和照片附件。每个布局都有完全不同的约束来实现它,因此一旦单元被初始化并为这些单元类型之一添加了约束,单元应该获得特定于该单元类型的唯一重用标识符。这意味着当您将单元出列以供重用时,约束已经添加并准备好用于该单元类型。
Note that due to differences in intrinsic content size, cells with the same constraints (type) may still have varying heights! Don't confuse fundamentally different layouts (different constraints) with different calculated view frames (solved from identical constraints) due to different sizes of content.
请注意,由于内在内容大小的差异,具有相同约束(类型)的单元格可能仍具有不同的高度!由于内容的大小不同,不要将根本不同的布局(不同的约束)与不同的计算视图框架(从相同的约束解决)混淆。
- Do not add cells with completely different sets of constraints to the same reuse pool (i.e. use the same reuse identifier) and then attempt to remove the old constraints and set up new constraints from scratch after each dequeue. The internal Auto Layout engine is not designed to handle large scale changes in constraints, and you will see massive performance issues.
- 不要将具有完全不同约束集的单元添加到同一个重用池中(即使用相同的重用标识符),然后在每次出队后尝试删除旧约束并从头开始设置新约束。内部自动布局引擎不是为了处理约束的大规模变化而设计的,你会看到大量的性能问题。
For iOS 8 - Self-Sizing Cells
对于 iOS 8 - 调整单元格大小
3. Enable Row Height Estimation
3. 启用行高估计
To enable self-sizing table view cells, you must set the table view's rowHeight property to UITableViewAutomaticDimension. You must also assign a value to the estimatedRowHeight property. As soon as both of these properties are set, the system uses Auto Layout to calculate the row's actual height
要启用自调整表格视图单元格,您必须将表格视图的 rowHeight 属性设置为 UITableViewAutomaticDimension。您还必须为estimatedRowHeight 属性分配一个值。一旦设置了这两个属性,系统就会使用自动布局来计算行的实际高度
Apple:使用自适应表格视图单元格
With iOS 8, Apple has internalized much of the work that previously had to be implemented by you prior to iOS 8. In order to allow the self-sizing cell mechanism to work, you must first set the rowHeight
property on the table view to the constant UITableViewAutomaticDimension
. Then, you simply need to enable row height estimation by setting the table view's estimatedRowHeight
property to a nonzero value, for example:
在 iOS 8 中,Apple 已经内化了之前必须由你在 iOS 8 之前实现的大部分工作。为了让自调整单元格机制起作用,你必须首先将rowHeight
table view 上的属性设置为常量UITableViewAutomaticDimension
. 然后,您只需要通过将表视图的estimatedRowHeight
属性设置为非零值来启用行高估计,例如:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
What this does is provide the table view with a temporary estimate/placeholder for the row heights of cells that are not yet onscreen. Then, when these cells are about to scroll on screen, the actual row height will be calculated. To determine the actual height for each row, the table view automatically asks each cell what height its contentView
needs to be based on the known fixed width of the content view (which is based on the table view's width, minus any additional things like a section index or accessory view) and the auto layout constraints you have added to the cell's content view and subviews. Once this actual cell height has been determined, the old estimated height for the row is updated with the new actual height (and any adjustments to the table view's contentSize/contentOffset are made as needed for you).
这样做是为表格视图提供一个临时估计/占位符,用于尚未出现在屏幕上的单元格的行高。然后,当这些单元格即将在屏幕上滚动时,将计算实际行高。为了确定每一行的实际高度,表格视图contentView
会根据内容视图的已知固定宽度(基于表格视图的宽度,减去任何其他内容,如节索引)自动询问每个单元格需要什么高度或附件视图)以及您添加到单元格内容视图和子视图的自动布局约束。一旦确定了该实际单元格高度,该行的旧估计高度将更新为新的实际高度(并且根据您的需要对表格视图的 contentSize/contentOffset 进行任何调整)。
Generally speaking, the estimate you provide doesn't have to be very accurate -- it is only used to correctly size the scroll indicator in the table view, and the table view does a good job of adjusting the scroll indicator for incorrect estimates as you scroll cells onscreen. You should set the estimatedRowHeight
property on the table view (in viewDidLoad
or similar) to a constant value that is the "average" row height. Only if your row heights have extreme variability (e.g. differ by an order of magnitude) and you notice the scroll indicator "jumping" as you scroll should you bother implementing tableView:estimatedHeightForRowAtIndexPath:
to do the minimal calculation required to return a more accurate estimate for each row.
一般来说,您提供的估计不必非常准确——它仅用于正确调整表格视图中滚动指示器的大小,并且表格视图可以很好地调整滚动指示器以适应不正确的估计在屏幕上滚动单元格。您应该将estimatedRowHeight
表视图(inviewDidLoad
或类似)上的属性设置为“平均”行高的常量值。只有当您的行高具有极大的可变性(例如,相差一个数量级)并且您在滚动时注意到滚动指示器“跳跃”时,您才应该费心实施tableView:estimatedHeightForRowAtIndexPath:
以执行返回每行更准确估计所需的最少计算。
For iOS 7 support (implementing auto cell sizing yourself)
对于 iOS 7 支持(自己实现自动单元格大小调整)
3. Do a Layout Pass & Get The Cell Height
3. 做一个布局传递并获取单元格高度
First, instantiate an offscreen instance of a table view cell, one instance for each reuse identifier, that is used strictly for height calculations. (Offscreen meaning the cell reference is stored in a property/ivar on the view controller and never returned from tableView:cellForRowAtIndexPath:
for the table view to actually render onscreen.) Next, the cell must be configured with the exact content (e.g. text, images, etc) that it would hold if it were to be displayed in the table view.
首先,实例化一个表格视图单元的屏幕外实例,每个重用标识符一个实例,严格用于高度计算。(离屏意味着单元格引用存储在视图控制器上的属性/ivar 中,并且永远不会从tableView:cellForRowAtIndexPath:
表视图返回以实际呈现在屏幕上。)接下来,必须使用确切的内容(例如文本、图像等)配置单元格如果它要显示在表格视图中,它将保持不变。
Then, force the cell to immediately layout its subviews, and then use the systemLayoutSizeFittingSize:
method on the UITableViewCell
's contentView
to find out what the required height of the cell is. Use UILayoutFittingCompressedSize
to get the smallest size required to fit all the contents of the cell. The height can then be returned from the tableView:heightForRowAtIndexPath:
delegate method.
然后,强制单元格立即布局其子视图,然后使用'ssystemLayoutSizeFittingSize:
上的方法找出单元格所需的高度。使用获得的最小尺寸必须适应单元格中的所有内容。然后可以从委托方法返回高度。UITableViewCell
contentView
UILayoutFittingCompressedSize
tableView:heightForRowAtIndexPath:
4. Use Estimated Row Heights
4. 使用估计的行高
If your table view has more than a couple dozen rows in it, you will find that doing the Auto Layout constraint solving can quickly bog down the main thread when first loading the table view, as tableView:heightForRowAtIndexPath:
is called on each and every row upon first load (in order to calculate the size of the scroll indicator).
如果你的表视图中有几十行,你会发现在第一次加载表视图时,执行自动布局约束求解会很快陷入主线程,就像在第一次加载时tableView:heightForRowAtIndexPath:
对每一行调用的那样(以计算滚动指示器的大小)。
As of iOS 7, you can (and absolutely should) use the estimatedRowHeight
property on the table view. What this does is provide the table view with a temporary estimate/placeholder for the row heights of cells that are not yet onscreen. Then, when these cells are about to scroll on screen, the actual row height will be calculated (by calling tableView:heightForRowAtIndexPath:
), and the estimated height updated with the actual one.
从 iOS 7 开始,您可以(并且绝对应该)estimatedRowHeight
在 table view 上使用该属性。这样做是为表格视图提供一个临时估计/占位符,用于尚未出现在屏幕上的单元格的行高。然后,当这些单元格即将在屏幕上滚动时,将计算实际行高(通过调用tableView:heightForRowAtIndexPath:
),并使用实际行高更新估计高度。
Generally speaking, the estimate you provide doesn't have to be very accurate -- it is only used to correctly size the scroll indicator in the table view, and the table view does a good job of adjusting the scroll indicator for incorrect estimates as you scroll cells onscreen. You should set the estimatedRowHeight
property on the table view (in viewDidLoad
or similar) to a constant value that is the "average" row height. Only if your row heights have extreme variability (e.g. differ by an order of magnitude) and you notice the scroll indicator "jumping" as you scroll should you bother implementing tableView:estimatedHeightForRowAtIndexPath:
to do the minimal calculation required to return a more accurate estimate for each row.
一般来说,您提供的估计不必非常准确——它仅用于正确调整表格视图中滚动指示器的大小,并且表格视图可以很好地调整滚动指示器以适应不正确的估计在屏幕上滚动单元格。您应该将estimatedRowHeight
表视图(inviewDidLoad
或类似)上的属性设置为“平均”行高的常量值。只有当您的行高具有极大的可变性(例如,相差一个数量级)并且您在滚动时注意到滚动指示器“跳跃”时,您才应该费心实施tableView:estimatedHeightForRowAtIndexPath:
以执行返回每行更准确估计所需的最少计算。
5. (If Needed) Add Row Height Caching
5.(如果需要)添加行高缓存
If you've done all the above and are still finding that performance is unacceptably slow when doing the constraint solving in tableView:heightForRowAtIndexPath:
, you'll unfortunately need to implement some caching for cell heights. (This is the approach suggested by Apple's engineers.) The general idea is to let the Autolayout engine solve the constraints the first time, then cache the calculated height for that cell and use the cached value for all future requests for that cell's height. The trick of course is to make sure you clear the cached height for a cell when anything happens that could cause the cell's height to change -- primarily, this would be when that cell's content changes or when other important events occur (like the user adjusting the Dynamic Type text size slider).
如果您已完成上述所有操作,但仍然发现在 中进行约束求解时性能低得令人无法接受tableView:heightForRowAtIndexPath:
,那么不幸的是,您将需要为单元格高度实现一些缓存。(这是 Apple 工程师建议的方法。)一般的想法是让 Autolayout 引擎第一次解决约束,然后缓存该单元格的计算高度,并将缓存的值用于该单元格高度的所有未来请求。当然,诀窍是确保在发生任何可能导致单元格高度发生变化的情况时清除单元格的缓存高度——主要是当该单元格的内容发生变化或其他重要事件发生时(例如用户调整动态类型文本大小滑块)。
iOS 7 Generic Sample Code (with lots of juicy comments)
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;
}
}
Sample Projects
示例项目
- iOS 8 Sample Project- Requires iOS 8
- iOS 7 Sample Project- Works on iOS 7+
- iOS 8 示例项目- 需要 iOS 8
- iOS 7 示例项目- 适用于 iOS 7+
These projects are fully working examples of table views with variable row heights due to table view cells containing dynamic content in UILabels.
由于表格视图单元格包含 UILabels 中的动态内容,这些项目是具有可变行高的表格视图的完整工作示例。
Xamarin (C#/.NET)
Xamarin (C#/.NET)
If you're using Xamarin, check out this sample projectput together by @KentBoogaart.
如果您使用的是 Xamarin,请查看由@KentBoogaart 整理的这个示例项目。
回答by William Hu
For iOS 8 above it's really simple:
对于上面的 iOS 8,它真的很简单:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 80
self.tableView.rowHeight = UITableView.automaticDimension
}
or
或者
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableView.automaticDimension
}
But for iOS 7, the key is calculate the height after autolayout:
但是对于iOS 7,关键是计算autolayout后的高度:
func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
cell.setNeedsLayout()
cell.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
return height
}
Important
重要的
If multiple lines labels, don't forget set the
numberOfLines
to0
.Don't forget
label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)
如果多行标签,不要忘记设置
numberOfLines
为0
。不要忘记
label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)
The full example code is here.
完整的示例代码在这里。
回答by Suragch
Swift example of a variable height UITableViewCell
可变高度 UITableViewCell 的 Swift 示例
Updated for Swift 3
为 Swift 3 更新
William Hu's Swift answer is good, but it helps me to have some simple yet detailed steps when learning to do something for the first time. The example below is my test project while learning to make a UITableView
with variable cell heights. I based it on this basic UITableView example for Swift.
William Hu 的 Swift 回答很好,但它帮助我在第一次学习做某事时有一些简单而详细的步骤。下面的示例是我在学习制作UITableView
可变单元格高度时的测试项目。我基于Swift 的这个基本 UITableView 示例。
The finished project should look like this:
完成的项目应该是这样的:
Create a new project
创建一个新项目
It can be just a Single View Application.
它可以只是一个单一视图应用程序。
Add the code
添加代码
Add a new Swift file to your project. Name it MyCustomCell. This class will hold the outlets for the views that you add to your cell in the storyboard. In this basic example we will only have one label in each cell.
将一个新的 Swift 文件添加到您的项目中。将其命名为 MyCustomCell。这个类将保存您添加到故事板中单元格的视图的出口。在这个基本示例中,我们将在每个单元格中只有一个标签。
import UIKit
class MyCustomCell: UITableViewCell {
@IBOutlet weak var myCellLabel: UILabel!
}
We will connect this outlet later.
我们稍后会连接这个插座。
Open ViewController.swift and make sure you have the following content:
打开 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).")
}
}
Important Note:
重要的提示:
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
以下两行代码(以及自动布局)使可变单元格高度成为可能:
tableView.estimatedRowHeight = 44.0 tableView.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.
将一个 Table View 添加到您的视图控制器并使用自动布局将其固定到四个边。然后将一个 Table View Cell 拖到 Table View 上。并在原型单元格上拖动一个标签。使用自动布局将标签固定到表格视图单元格内容视图的四个边缘。
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
其他 IB 设置
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.
选择Table View Cell,设置自定义类为MyCustomCell
(我们添加的Swift文件中的类名)。还将标识符设置为cell
(与我们cellReuseIdentifier
在上面的代码中使用的字符串相同的字符串。
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.
0
在标签中设置行数。这意味着多行并允许标签根据其内容调整自身大小。
Hook Up the Outlets
连接插座
- Control drag from the Table View in the storyboard to the
tableView
variable in theViewController
code. - Do the same for the Label in your Prototype cell to the
myCellLabel
variable in theMyCustomCell
class.
- 控制从 storyboard 中的 Table View 拖动到代码中的
tableView
变量ViewController
。 - 对 Prototype 单元格中的 Label
myCellLabel
对MyCustomCell
类中的变量执行相同的操作。
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 articlefor 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 thetableView:cellForRowAtIndexPath
method:cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
- 此示例仅适用于 iOS 8 及更高版本。如果您仍然需要支持 iOS 7,那么这对您不起作用。
- 在您未来的项目中,您自己的自定义单元格可能会有多个标签。确保您将所有内容固定正确,以便自动布局可以确定要使用的正确高度。您可能还必须使用垂直压缩阻力和拥抱。有关更多信息,请参阅这篇文章。
如果您没有固定前缘和后缘(左右),您可能还需要设置标签,
preferredMaxLayoutWidth
以便它知道何时换行。例如,如果您在上面的项目中为标签添加了水平居中约束而不是固定前缘和后缘,那么您需要将此行添加到tableView:cellForRowAtIndexPath
方法中:cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
See also
也可以看看
回答by Adam Waite
I wrapped @smileyborg's iOS7 solution in a category
我将@smileyborg 的 iOS7 解决方案包装在一个类别中
I decided to wrap this clever solution by @smileyborg into a UICollectionViewCell+AutoLayoutDynamicHeightCalculation
category.
我决定将@smileyborg 的这个聪明的解决方案包装到一个UICollectionViewCell+AutoLayoutDynamicHeightCalculation
类别中。
The category also rectifies the issues outlined in @wildmonkey's answer (loading a cell from a nib and systemLayoutSizeFittingSize:
returning CGRectZero
)
该类别还纠正了@wildmonkey 的回答中概述的问题(从笔尖加载单元格并systemLayoutSizeFittingSize:
返回CGRectZero
)
It doesn't take into account any caching but suits my needs right now. Feel free to copy, paste and hack at it.
它没有考虑任何缓存,但适合我现在的需求。随意复制、粘贴和破解它。
UICollectionViewCell+AutoLayoutDynamicHeightCalculation.h
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
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
Usage example:
用法示例:
- (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);
}
Thankfully we won't have to do this jazz in iOS8, but there it is for now!
谢天谢地,我们不必在 iOS8 中做这种爵士乐,但现在就可以了!
回答by Eddwin Paz
Here is my solution:
这是我的解决方案:
You need to tell the TableView
the estimatedHeight
before it loads the view. Otherwise it wont be able to behave like expected.
你需要告诉TableView
的estimatedHeight
它加载视图之前。否则它将无法像预期的那样运行。
Objective-C
目标-C
- (void)viewWillAppear:(BOOL)animated {
_messageField.delegate = self;
_tableView.estimatedRowHeight = 65.0;
_tableView.rowHeight = UITableViewAutomaticDimension;
}
Update to Swift 4.2
更新到Swift 4.2
override func viewWillAppear(_ animated: Bool) {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 65.0
}
回答by wildmonkey
The solution proposed by @smileyborg is almost perfect. If you have a custom cell and you want one or more UILabel
with dynamic heights then the systemLayoutSizeFittingSizemethod combined with AutoLayout enabled returns a CGSizeZero
unless you move all your cell constraints from the cell to its contentView (as suggested by @TomSwift here How to resize superview to fit all subviews with autolayout?).
@smileyborg 提出的解决方案几乎是完美的。如果您有一个自定义单元格,并且您想要一个或多个UILabel
具有动态高度的单元格,那么systemLayoutSizeFittingSize方法与启用AutoLayout相结合会返回 a,CGSizeZero
除非您将所有单元格约束从单元格移动到其 contentView(如@TomSwift 在这里建议的如何将超级视图调整为使用自动布局适合所有子视图?)。
To do so you need to insert the following code in your custom UITableViewCell implementation (thanks to @Adrian).
为此,您需要在自定义 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];
}
}
Mixing @smileyborg answer with this should works.
将@smileyborg 的答案与此混合应该有效。
回答by Bob Spryn
An important enough gotcha I just ran into to post as an answer.
一个足够重要的问题,我刚刚遇到并作为答案发布。
@smileyborg's answer is mostly correct. However, if you have any code in the layoutSubviews
method of your custom cell class, for instance setting the preferredMaxLayoutWidth
, then it won't be run with this code:
@smileyborg 的回答大部分是正确的。但是,如果您layoutSubviews
的自定义单元格类的方法中有任何代码,例如设置preferredMaxLayoutWidth
,则不会使用以下代码运行:
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
It confounded me for awhile. Then I realized it's because those are only triggering layoutSubviews on the contentView
, not the cell itself.
这让我困惑了一段时间。然后我意识到这是因为那些只是在 上触发 layoutSubviews contentView
,而不是单元格本身。
My working code looks like this:
我的工作代码如下所示:
TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
Note that if you are creating a new cell, I'm pretty sure you don't need to call setNeedsLayout
as it should already be set. In cases where you save a reference to a cell, you should probably call it. Either way it shouldn't hurt anything.
请注意,如果您正在创建一个新单元格,我很确定您不需要调用,setNeedsLayout
因为它应该已经设置好了。在保存对单元格的引用的情况下,您可能应该调用它。无论哪种方式,它都不应该伤害任何东西。
Another tip if you are using cell subclasses where you are setting things like preferredMaxLayoutWidth
. As @smileyborg mentions, "your table view cell hasn't yet had its width fixed to the table view's width". This is true, and trouble if you are doing your work in your subclass and not in the view controller. However you can simply set the cell frame at this point using the table width:
如果您在使用单元格子类的地方设置诸如preferredMaxLayoutWidth
. 正如@smileyborg 提到的,“您的表格视图单元格的宽度尚未固定为表格视图的宽度”。这是真的,如果你在你的子类中而不是在视图控制器中做你的工作,那就麻烦了。但是,此时您可以使用表格宽度简单地设置单元格框架:
For instance in the calculation for height:
例如在计算高度时:
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);
(I happen to cache this particular cell for re-use, but that's irrelevant).
(我碰巧缓存了这个特定的单元以供重用,但这无关紧要)。
回答by Alex Rouse
In case people are still having trouble with this. I wrote a quick blog post about using Autolayout with UITableViews Leveraging Autolayout For Dynamic Cell Heightsas well as an open source component to help make this more abstract and easier to implement. https://github.com/Raizlabs/RZCellSizeManager
万一人们仍然有这个问题。我写了一篇关于将 Autolayout 与 UITableViews 结合使用的快速博客文章,利用 Autolayout For Dynamic Cell Heights以及一个开源组件来帮助使其更抽象和更容易实现。 https://github.com/Raizlabs/RZCellSizeManager
回答by Chris Van Buskirk
As long as your layout in your cell is good.
只要您在单元格中的布局良好。
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}
Update: You should use dynamic resizing introduced in iOS 8.
更新:您应该使用 iOS 8 中引入的动态调整大小。
回答by Nikolay Suvandzhiev
(for Xcode 8.x / Xcode 9.x read at the bottom)
(对于 Xcode 8.x / Xcode 9.x 在底部读取)
Beware of the following issue in in Xcode 7.x, which might be a source of confusion:
请注意 Xcode 7.x 中的以下问题,这可能会引起混淆:
Interface Builder does not handle auto-sizing cell set-up properly. Even if your constraints are absolutely valid, IB will still complain and give you confusing suggestions and errors. The reason is that IB is unwilling to change the row's height as your constraints dictate (so that the cell fits around your content). Instead, it keeps the row's height fixed and starts suggesting you change your constraints, which you should ignore.
Interface Builder 无法正确处理自动调整大小的单元格设置。即使你的约束是绝对有效的,IB 仍然会抱怨并给你混乱的建议和错误。原因是 IB 不愿意按照您的约束要求更改行的高度(以便单元格适合您的内容)。相反,它保持行的高度固定并开始建议您更改您的约束,您应该忽略。
For example, imagine you've set up everything fine, no warnings, no errors, all works.
例如,假设您已经设置好一切,没有警告,没有错误,一切正常。
Now if you change the font size (in this example I'm changing the description label font size from 17.0 to 18.0).
现在,如果您更改字体大小(在本示例中,我将描述标签字体大小从 17.0 更改为 18.0)。
Because the font size increased, the label now wants to occupy 3 rows (before that it was occupying 2 rows).
因为字体变大了,标签现在要占据 3 行(之前它是占据 2 行)。
If Interface Builder worked as expected, it would resize the cell's height to accommodate the new label height. However what actually happens is that IB displays the red auto-layout error icon and suggest that you modify hugging/compression priorities.
如果 Interface Builder 按预期工作,它将调整单元格的高度以适应新的标签高度。然而,实际发生的是 IB 显示红色自动布局错误图标并建议您修改拥抱/压缩优先级。
You should ignore these warnings. What you can* do instead is to manually change the row's height in (select Cell > Size Inspector > Row Height).
您应该忽略这些警告。您可以*做的是手动更改行的高度(选择单元格>大小检查器>行高)。
I was changing this height one click at a time (using the up/down stepper) until the red arrow errors disappear! (you will actually get yellow warnings, at which point just go ahead and do 'update frames', it should all work).
我一次单击一次(使用向上/向下步进器)更改此高度,直到红色箭头错误消失!(您实际上会收到黄色警告,此时只需继续执行“更新帧”,它应该都能正常工作)。
* Note that you don't actually have to resolve these red errors or yellow warnings in Interface Builder - at runtime, everything will work correctly (even if IB shows errors/warnings). Just make sure that at runtime in the console log you're not getting any AutoLayout errors.
In fact trying to always update row height in IB is super annoying and sometimes close to impossible (because of fractional values).
To prevent the annoying IB warnings/errors, you can select the views involved and in Size Inspector
for the property Ambiguity
choose Verify Position Only
* 请注意,您实际上不必解决 Interface Builder 中的这些红色错误或黄色警告 - 在运行时,一切都会正常工作(即使 IB 显示错误/警告)。只需确保在运行时在控制台日志中您没有收到任何 AutoLayout 错误。
事实上,试图总是在 IB 中更新行高是非常烦人的,有时几乎不可能(因为小数值)。
为了防止烦人的 IB 警告/错误,您可以选择所涉及的视图并在Size Inspector
属性中Ambiguity
选择Verify Position Only
Xcode 8.x / Xcode 9.x seems to (sometimes) be doing things differently than Xcode 7.x, but still incorrectly. For example even when compression resistance priority
/ hugging priority
are set to required (1000), Interface Builder might stretch or clip a label to fit the cell (instead of resizing cell height to fit around the label). And in such a case it might not even show any AutoLayout warnings or errors. Or sometimes it does exactly what Xcode 7.x did, described above.
Xcode 8.x / Xcode 9.x 似乎(有时)做的事情与 Xcode 7.x 不同,但仍然不正确。例如,即使compression resistance priority
/hugging priority
设置为 required (1000),Interface Builder 也可能会拉伸或剪切标签以适合单元格(而不是调整单元格高度以适合标签周围)。在这种情况下,它甚至可能不会显示任何 AutoLayout 警告或错误。或者有时它完全符合 Xcode 7.x 所做的,如上所述。