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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:06:43  来源:igfitidea点击:

Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

iosuitableviewautolayoutnsautolayoutrow-height

提问by smileyborg

How do you use Auto Layout within UITableViewCells 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 上的示例项目:

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 UITableViewCellsubclass, 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:

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

Example illustration of constraints on a table view cell.

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

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 updateConstraintsmethod of your UITableViewCell subclass. Note that updateConstraintsmay be called more than once, so to avoid adding the same constraints more than once, make sure to wrap your constraint-adding code within updateConstraintsin a check for a boolean property such as didSetupConstraints(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 the constantproperty on some constraints), place this in updateConstraintsbut outside of the check for didSetupConstraintsso it can run every time the method is called.
  • 如果你在代码中添加约束,你应该在updateConstraintsUITableViewCell 子类的方法中执行一次。请注意,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

Apple: Working with Self-Sizing Table View Cells

要启用自调整表格视图单元格,您必须将表格视图的 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 rowHeightproperty on the table view to the constant UITableViewAutomaticDimension. Then, you simply need to enable row height estimation by setting the table view's estimatedRowHeightproperty to a nonzero value, for example:

在 iOS 8 中,Apple 已经内化了之前必须由你在 iOS 8 之前实现的大部分工作。为了让自调整单元格机制起作用,你必须首先将rowHeighttable 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 contentViewneeds 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 estimatedRowHeightproperty on the table view (in viewDidLoador 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 contentViewto find out what the required height of the cell is. Use UILayoutFittingCompressedSizeto 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:上的方法找出单元格所需的高度。使用获得的最小尺寸必须适应单元格中的所有内容。然后可以从委托方法返回高度。UITableViewCellcontentViewUILayoutFittingCompressedSizetableView: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 estimatedRowHeightproperty 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 estimatedRowHeightproperty on the table view (in viewDidLoador 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

示例项目

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 numberOfLinesto 0.

  • Don't forget label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

  • 如果多行标签,不要忘记设置numberOfLines0

  • 不要忘记 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 UITableViewwith 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:

完成的项目应该是这样的:

enter image description here

在此处输入图片说明

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 上。并在原型单元格上拖动一个标签。使用自动布局将标签固定到表格视图单元格内容视图的四个边缘。

enter image description here

在此处输入图片说明

Important note:

重要的提示:

  • Auto layout works together with the important two lines of code I mentioned above. If you don't use auto layout it isn't going to work.
  • 自动布局与我上面提到的两行重要代码一起工作。如果您不使用自动布局,它将无法正常工作。

Other IB settings

其他 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 cellReuseIdentifierin the code above.

选择Table View Cell,设置自定义类为MyCustomCell(我们添加的Swift文件中的类名)。还将标识符设置为cell(与我们cellReuseIdentifier在上面的代码中使用的字符串相同的字符串。

enter image description here

在此处输入图片说明

Zero Lines for Label

标签的零线

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

0在标签中设置行数。这意味着多行并允许标签根据其内容调整自身大小。

enter image description here

在此处输入图片说明

Hook Up the Outlets

连接插座

  • Control drag from the Table View in the storyboard to the tableViewvariable in the ViewControllercode.
  • Do the same for the Label in your Prototype cell to the myCellLabelvariable in the MyCustomCellclass.
  • 控制从 storyboard 中的 Table View 拖动到代码中的tableView变量ViewController
  • 对 Prototype 单元格中的 LabelmyCellLabelMyCustomCell类中的变量执行相同的操作。

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 preferredMaxLayoutWidthso 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:cellForRowAtIndexPathmethod:

     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+AutoLayoutDynamicHeightCalculationcategory.

我决定将@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 TableViewthe estimatedHeightbefore it loads the view. Otherwise it wont be able to behave like expected.

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

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 UILabelwith dynamic heights then the systemLayoutSizeFittingSizemethod combined with AutoLayout enabled returns a CGSizeZerounless 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 layoutSubviewsmethod 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 setNeedsLayoutas 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.

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

enter image description here

在此处输入图片说明

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)。

enter image description here

在此处输入图片说明

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 显示红色自动布局错误图标并建议您修改拥抱/压缩优先级。

enter image description here

在此处输入图片说明

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).

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

enter image description here

在此处输入图片说明

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 Inspectorfor the property Ambiguitychoose Verify Position Only

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

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

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

enter image description here

在此处输入图片说明



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 priorityare 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 所做的,如上所述。