简体   繁体   English

在 UITableView 中使用自动布局进行动态单元格布局和可变行高

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

如何在表格视图中的UITableViewCell使用 Auto Layout 来让每个单元格的内容和子视图确定行高(本身/自动),同时保持平滑的滚动性能?

TL;DR: Don't like reading? TL;DR:不喜欢阅读? Jump straight to the sample projects on GitHub:直接跳转到 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 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).在你UITableViewCell子类,添加约束,使细胞的子视图具有其边缘固定到该单元的内容查看的(最重要的顶部和底部边缘)的边缘。 NOTE: don't pin subviews to the cell itself;注意:不要将子视图固定到单元格本身; only to the cell's contentView !仅到单元格的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 resistance and content hugging constraints in the vertical dimension for each subview are not being overridden by higher-priority constraints you have added.通过确保每个子视图的垂直维度的内容压缩阻力内容拥抱约束不会被您添加的更高优先级约束覆盖,让这些子视图的内在内容大小驱动表格视图单元格内容视图的高度。 ( Huh? Click here. ) 嗯?点这里。

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 part of 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.如果你在代码中添加约束,你应该在 UITableViewCell 子类的updateConstraints方法中执行一次。 Note that updateConstraints may be called more than once, so to avoid adding the same constraints more than once, make sure to wrap your constraint-adding code within updateConstraints in a check for a boolean property such as didSetupConstraints (which you set to YES after you run your constraint-adding code once).请注意, updateConstraints可能会被多次调用,因此为了避免多次添加相同的约束,请确保将添加约束的代码包装在updateConstraints中,以检查是否存在诸如didSetupConstraints类的布尔属性(在执行后将其设置为 YES)运行一次添加约束的代码)。 On the other hand, if you have code that updates existing constraints (such as adjusting the constant property on some constraints), place this in updateConstraints but outside of the check for didSetupConstraints so it can run every time the method is called.另一方面,如果您有更新现有约束的代码(例如调整某些约束的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.例如,如果您在每个单元格中显示一封电子邮件,您可能有 4 种独特的布局:只有一个主题的邮件、一个主题和一个正文的邮件、一个主题和一个照片附件的邮件以及一个主题的邮件,身体和照片附件。 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.这意味着当您将单元出列以供重用时,约束已经添加并准备好用于该单元类型。

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 (ie 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.要启用自调整表格视图单元格,您必须将表格视图的 rowHeight 属性设置为 UITableViewAutomaticDimension。 You must also assign a value to the estimatedRowHeight property.您还必须为estimatedRowHeight 属性分配一个值。 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 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 UITableView.automaticDimension .在 iOS 8 中,Apple 已经内化了之前必须由您在 iOS 8 之前实现的大部分工作。为了允许自调整单元格机制起作用,您必须首先将 table view 上的rowHeight属性设置为常量UITableView.automaticDimension Then, you simply need to enable row height estimation by setting the table view's estimatedRowHeight property to a nonzero value, for example:然后,您只需要通过将表视图的estimatedRowHeight属性设置为非零值来启用行高估计,例如:

self.tableView.rowHeight = UITableView.automaticDimension;
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.为了确定每一行的实际高度,表格视图会根据内容视图的已知固定宽度(基于表格视图的宽度,减去任何其他内容,如部分)自动询问每个单元格的contentView需要什么高度索引或附件视图)以及您添加到单元格内容视图和子视图的自动布局约束。 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).一旦确定了该实际单元格高度,该行的旧估计高度将更新为新的实际高度(并且根据您的需要对表格视图的 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.您应该将表视图(在viewDidLoad或类似的)上的estimatedRowHeight属性设置为一个常量值,即“平均”行高。 Only if your row heights have extreme variability (eg 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.只有当您的行高具有极大的可变性(例如,相差一个数量级)并且您在滚动时注意到滚动指示器“跳跃”时,您才应该费心实施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 (eg 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.然后,强制单元格立即布局其子视图,然后使用UITableViewCellcontentView上的systemLayoutSizeFittingSize:方法找出单元格所需的高度。 Use UILayoutFittingCompressedSize to get the smallest size required to fit all the contents of the cell.使用UILayoutFittingCompressedSize获得适合单元格所有内容所需的最小尺寸。 The height can then be returned from the tableView:heightForRowAtIndexPath: delegate method.然后可以从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).如果你的 table view 有几十行,你会发现在第一次加载 table view 时,做 Auto Layout 约束求解会很快卡住主线程,因为tableView:heightForRowAtIndexPath:会在每一行上调用在第一次加载时(为了计算滚动指示器的大小)。

As of iOS 7, you can (and absolutely should) use the estimatedRowHeight property on the table view.从 iOS 7 开始,您可以(并且绝对应该)在 table view 上使用estimatedRowHeight属性。 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.然后,当这些单元格即将在屏幕上滚动时,将计算实际行高(通过调用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.您应该将表视图(在viewDidLoad或类似的)上的estimatedRowHeight属性设置为一个常量值,即“平均”行高。 Only if your row heights have extreme variability (eg 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.只有当您的行高具有极大的可变性(例如,相差一个数量级)并且您在滚动时注意到滚动指示器“跳跃”时,您才应该费心实施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.如果您已完成上述所有操作,但仍然发现在tableView:heightForRowAtIndexPath:进行约束求解时性能慢得令人无法接受,那么不幸的是,您将需要为单元格高度实现一些缓存。 (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. (这是 Apple 工程师建议的方法。)一般的想法是让 Autolayout 引擎第一次解决约束,然后缓存该单元格的计算高度,并将缓存的值用于该单元格高度的所有未来请求。 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).当然,诀窍是确保在发生任何可能导致单元格高度发生变化的情况时清除单元格的缓存高度——主要是当该单元格的内容发生变化或其他重要事件发生时(例如用户调整动态类型文本大小滑块)。

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 project put together by @KentBoogaart .如果您使用的是 Xamarin,请查看由@KentBoogaart 整理的这个示例项目

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 to 0 .如果有多行标签,不要忘记将numberOfLines设置为0

  • Don't forget label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)不要忘记label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

The full example code is here .完整的示例代码在这里

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. William Hu 的 Swift 回答很好,但它帮助我在第一次学习做某事时有一些简单而详细的步骤。 The example below is my test project while learning to make a UITableView with variable cell heights.下面的示例是我在学习制作具有可变单元格高度的UITableView时的测试项目。 I based it on this basic UITableView example for Swift .我基于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.将一个新的 Swift 文件添加到您的项目中。 Name it MyCustomCell.将其命名为 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.在这个基本示例中,我们将在每个单元格中只有一个标签。

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

Setup the storyboard设置故事板

Add a Table View to your view controller and use auto layout to pin it to the four sides.将一个 Table View 添加到您的视图控制器并使用自动布局将其固定到四个边。 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.使用自动布局将标签固定到表格视图单元格内容视图的四个边缘。

在此处输入图片说明

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).选择Table View Cell,设置自定义类为MyCustomCell (我们添加的Swift文件中的类名)。 Also set the Identifier to be cell (the same string that we used for the cellReuseIdentifier in the code above. cellReuseIdentifier标识符设置为cell (与我们在上面代码中用于cellReuseIdentifier字符串相同。

在此处输入图片说明

Zero Lines for Label标签的零线

Set the number of lines to 0 in your Label.在标签中将行数设置为0 This means multi-line and allows the label to resize itself based on its content.这意味着多行并允许标签根据其内容调整自身大小。

在此处输入图片说明

Hook Up the Outlets连接插座

  • Control drag from the Table View in the storyboard to the tableView variable in the ViewController code.控制从 storyboard 中的 Table View 拖动到ViewController代码中的tableView变量。
  • Do the same for the Label in your Prototype cell to the myCellLabel variable in the MyCustomCell class.对原型单元格中的myCellLabelMyCustomCell类中的myCellLabel变量执行相同的myCellLabel

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.此示例仅适用于 iOS 8 及更高版本。 If you are still needing to support iOS 7 then this won't work for you.如果您仍然需要支持 iOS 7,那么这对您不起作用。
  • 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 article for 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.如果您没有固定前缘和后缘(左右),您可能还需要设置标签的preferredMaxLayoutWidth以便它知道何时换行。 For example, if you had added a Center Horizontally constraint to the label in the project above rather than pin the leading and trailing edges, then you would need to add this line to the tableView:cellForRowAtIndexPath method:例如,如果您在上面的项目中为标签添加了水平居中约束而不是固定前缘和后缘,那么您需要将此行添加到tableView:cellForRowAtIndexPath方法:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

See also也可以看看

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:加载单元格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 中做这种爵士乐,但现在就可以了!

Here is my solution:这是我的解决方案:

You need to tell the TableView the estimatedHeight before it loads the view.您需要在加载视图之前告诉TableView estimatedHeight TableView Otherwise it wont be able to behave like expected.否则它将无法像预期的那样运行。

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
}

The solution proposed by @smileyborg is almost perfect. @smileyborg 提出的解决方案几乎是完美的。 If you have a custom cell and you want one or more UILabel with dynamic heights then the systemLayoutSizeFittingSize method 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? ).如果您有一个自定义单元格并且您想要一个或多个具有动态高度的UILabel ,那么systemLayoutSizeFittingSize方法与启用AutoLayout相结合将返回一个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 的答案与此混合应该有效。

An important enough gotcha I just ran into to post as an answer.一个足够重要的问题,我刚刚遇到并作为答案发布。

@smileyborg's answer is mostly correct. @smileyborg 的回答大部分是正确的。 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:但是,如果您在自定义单元格类的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.然后我意识到这是因为那些只触发contentView上的 layoutSubviews ,而不是单元格本身。

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.请注意,如果您正在创建一个新单元格,我很确定您不需要调用setNeedsLayout因为它应该已经设置好了。 In cases where you save a reference to a cell, you should probably call it.在保存对单元格的引用的情况下,您可能应该调用它。 Either way it shouldn't hurt anything.无论哪种方式,它都不应该伤害任何东西。

Another tip if you are using cell subclasses where you are setting things like preferredMaxLayoutWidth .另一个提示,如果您使用单元格子类来设置诸如preferredMaxLayoutWidth As @smileyborg mentions, "your table view cell hasn't yet had its width fixed to the table view's width".正如@smileyborg 提到的,“您的表格视图单元格的宽度尚未固定为表格视图的宽度”。 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:但是,此时您可以使用表格宽度简单地设置单元格框架:

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). (我碰巧缓存了这个特定的单元以供重用,但这无关紧要)。

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 Heights as well as an open source component to help make this more abstract and easier to implement.我写了一篇关于将 Autolayout 与 UITableViews 结合使用的快速博客文章, 利用 Autolayout For Dynamic Cell Heights以及一个开源组件来帮助使其更抽象和更容易实现。 https://github.com/Raizlabs/RZCellSizeManager https://github.com/Raizlabs/RZCellSizeManager

(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. Interface Builder 无法正确处理自动调整大小的单元格设置。 Even if your constraints are absolutely valid, IB will still complain and give you confusing suggestions and errors.即使你的约束是绝对有效的,IB 仍然会抱怨并给你混乱的建议和错误。 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).原因是 IB 不愿意按照您的约束要求更改行的高度(以便单元格适合您的内容)。 Instead, it keeps the row's height fixed and starts suggesting you change your constraints, which you should ignore .相反,它保持行的高度固定并开始建议您更改您的约束,您应该忽略

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.如果 Interface Builder 按预期工作,它将调整单元格的高度以适应新的标签高度。 However what actually happens is that IB displays the red auto-layout error icon and suggest that you modify hugging/compression priorities.然而,实际发生的是 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). * 请注意,您实际上不必在 Interface Builder 中解决这些红色错误或黄色警告 - 在运行时,一切都会正常工作(即使 IB 显示错误/警告)。 Just make sure that at runtime in the console log you're not getting any AutoLayout errors.只需确保在运行时在控制台日志中您没有收到任何 AutoLayout 错误。

In fact trying to always update row height in IB is super annoying and sometimes close to impossible (because of fractional values).事实上,试图总是在 IB 中更新行高是非常烦人的,有时几乎不可能(因为小数值)。

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为了防止烦人的 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. Xcode 8.x / Xcode 9.x 似乎(有时)做的事情与 Xcode 7.x 不同,但仍然不正确。 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).例如,即使compression resistance priority / hugging priority设置为 required (1000),Interface Builder 也可能会拉伸或剪裁标签以适合单元格(而不是调整单元格高度以适合标签周围)。 And in such a case it might not even show any AutoLayout warnings or errors.在这种情况下,它甚至可能不会显示任何 AutoLayout 警告或错误。 Or sometimes it does exactly what Xcode 7.x did, described above.或者有时它完全符合 Xcode 7.x 所做的,如上所述。

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 中引入的动态调整大小。

To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.要为行高和估计行高设置自动尺寸,请确保按照以下步骤使自动尺寸对单元格/行高布局有效。

  • Assign and implement tableview dataSource and delegate分配和实现 tableview 数据源和委托
  • Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeightUITableViewAutomaticDimension分配给 rowHeight &estimatedRowHeight
  • Implement delegate/dataSource methods (ie heightForRowAt and return a value UITableViewAutomaticDimension to it)实现委托/数据源方法(即heightForRowAt并返回一个值UITableViewAutomaticDimension给它)

- ——

Objective C:目标 C:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.table.dataSource = self;
    self.table.delegate = self;

    self.table.rowHeight = UITableViewAutomaticDimension;
    self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    return UITableViewAutomaticDimension;
}

Swift:迅速:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Don't forget to set dataSource and delegate for table
    table.dataSource = self
    table.delegate = self

    // Set automatic dimensions for row height
    // Swift 4.2 onwards
    table.rowHeight = UITableView.automaticDimension
    table.estimatedRowHeight = UITableView.automaticDimension


    // Swift 4.1 and below
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension

}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    // Swift 4.2 onwards
    return UITableView.automaticDimension

    // Swift 4.1 and below
    return UITableViewAutomaticDimension
}

For label instance in UITableviewCell对于 UITableviewCell 中的标签实例

  • Set number of lines = 0 (& line break mode = truncate tail)设置行数 = 0(& 换行模式 = 截尾)
  • Set all constraints (top, bottom, right left) with respect to its superview/ cell container.设置与其父视图/单元格容器相关的所有约束(顶部、底部、左侧)。
  • Optional : Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.可选:设置标签的最小高度,如果你想要标签覆盖的最小垂直区域,即使没有数据。

在此处输入图片说明

Note : If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.注意:如果您有多个具有动态长度的标签(UIElements),应根据其内容大小进行调整:为您希望以更高优先级展开/压缩的标签调整“内容拥抱和压缩阻力优先级”。

Like @Bob-Spryn I ran into an important enough gotcha that I'm posting this as an answer.就像@Bob-Spryn 一样,我遇到了一个足够重要的问题,因此我将其发布为答案。

I struggled with @smileyborg's answer for a while.我在@smileyborg 的回答中挣扎了一段时间。 The gotcha that I ran into is if you've defined your prototype cell in IB with additional elements ( UILabels , UIButtons , etc.) in IB when you instantiate the cell with [ [YourTableViewCellClass alloc] init] it will not instantiate all the other elements within that cell unless you've written code to do that.我遇到的问题是,如果您在 IB 中使用其他元素( UILabelsUIButtons等)在 IB 中定义了原型单元格,当您使用 [ [YourTableViewCellClass alloc] init]实例化单元格时,它不会实例化所有其他[YourTableViewCellClass alloc] init]除非您编写了代码来执行此操作,否则该单元格中的元素。 (I had a similar experience with initWithStyle .) (我对initWithStyle有类似的经历。)

To have the storyboard instantiate all the additional elements obtain your cell with [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (Not [tableView dequeueReusableCellWithIdentifier:forIndexPath:] as this'll cause interesting problems.) When you do this all the elements you defined in IB will be instantiated.要让故事板实例化所有附加元素,请使用[tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (不是[tableView dequeueReusableCellWithIdentifier:forIndexPath:]因为这会导致有趣的问题。) IB 将被实例化。

Dynamic Table View Cell Height and Auto Layout 动态表格视图单元格高度和自动布局

A good way to solve the problem with storyboard Auto Layout:一个解决storyboard 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;
}

Another "solution": skip all this frustration and use a UIScrollView instead to get a result that looks and feels identical to UITableView.另一个“解决方案”:跳过所有这些挫折并使用 UIScrollView 来获得与 UITableView 外观和感觉相同的结果。

That was the painful "solution" for me, after having put in literally 20+ very frustrating hours total trying to build something like what smileyborg suggested and failing over many months and three versions of App Store releases.这对我来说是一个痛苦的“解决方案”,在我总共投入了 20 多个非常令人沮丧的小时试图构建类似 Smileyborg 建议的东西并且失败了几个月和三个版本的 App Store 发布之后。

My take is that if you really need iOS 7 support (for us, it's essential) then the technology is just too brittle and you'll pull your hair out trying.我的看法是,如果你真的需要 iOS 7 支持(对我们来说,这是必不可少的),那么这项技术太脆弱了,你会费力去尝试。 And that UITableView is complete overkill generally unless you're using some of the advanced row editing features and/or really need to support 1000+ "rows" (in our app, it's realistically never more than 20 rows).而且 UITableView 通常是完全矫枉过正的,除非您使用一些高级行编辑功能和/或确实需要支持 1000 多个“行”(在我们的应用程序中,它实际上永远不会超过 20 行)。

The added bonus is that the code gets insanely simple versus all the delegate crap and back and forth that comes with UITableView.额外的好处是,与 UITableView 附带的所有委托废话和来回相比,代码变得非常简单。 It's just one single loop of code in viewOnLoad that looks elegant and is easy to manage.它只是 viewOnLoad 中的一个单循环代码,看起来优雅且易于管理。

Here are some tips on how to do it:以下是有关如何执行此操作的一些提示:

  1. Using either Storyboard or a nib file, create a ViewController and associated root view.使用 Storyboard 或 nib 文件,创建一个 ViewController 和关联的根视图。

  2. Drag over a UIScrollView onto your root view.将 UIScrollView 拖到您的根视图上。

  3. Add constraints top, bottom, left, and right constraints to the top-level view so the UIScrollView fills the entire root view.将约束顶部、底部、左侧和右侧约束添加到顶级视图,以便 UIScrollView 填充整个根视图。

  4. Add a UIView inside the UIScrollView and call it "container".在 UIScrollView 中添加一个 UIView 并将其称为“容器”。 Add top, bottom, left and right constraints to the UIScrollView (its parent).向 UIScrollView(其父级)添加顶部、底部、左侧和右侧约束。 KEY TRICK: Also add an "Equal widths" constraint to link the UIScrollView and UIView.关键技巧:还要添加一个“等宽”约束来链接 UIScrollView 和 UIView。

    NOTE: You will get an error "scroll view has ambiguous scrollable content height" and that your container UIView should have a height of 0 pixels.注意:您将收到错误消息“滚动视图具有不明确的可滚动内容高度”,并且您的容器 UIView 的高度应为 0 像素。 Neither error seems to matter when the app is running.当应用程序运行时,这两个错误似乎都不重要。

  5. Create nib files and controllers for each of your "cells".为每个“单元”创建 nib 文件和控制器。 Use UIView not UITableViewCell.使用 UIView 而不是 UITableViewCell。

  6. In your root ViewController, you essentially add all the "rows" to the container UIView and programmatically add constraints linking their left and right edges to the container view, their top edges to either the container view top (for the first item) or the previous cell.在您的根 ViewController 中,您基本上将所有“行”添加到容器 UIView 并以编程方式添加约束,将它们的左右边缘链接到容器视图,将它们的顶部边缘链接到容器视图顶部(对于第一项)或前一个细胞。 Then link the final cell to the container bottom.然后将最后一个单元格链接到容器底部。

For us, each "row" is in a nib file.对我们来说,每一“行”都在一个 nib 文件中。 So the code looks something like this:所以代码看起来像这样:

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!)
    }
}

And here's the code for UITools.addViewToTop:这是 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))
    }
}

The only "gotcha" I've found with this approach so far is that UITableView has a nice feature of "floating" section headers at the top of the view as you scroll.到目前为止,我发现这种方法唯一的“问题”是 UITableView 有一个很好的功能,即滚动时视图顶部的“浮动”部分标题。 The above solution won't do that unless you add more programming but for our particular case this feature wasn't 100% essential and nobody noticed when it went away.除非您添加更多编程,否则上述解决方案不会这样做,但对于我们的特殊情况,此功能不是 100% 必不可少的,并且当它消失时没有人注意到。

If you want dividers between your cells, just add a 1 pixel high UIView at the bottom of your custom "cell" that looks like a divider.如果您想在单元格之间设置分隔线,只需在自定义“单元格”底部添加一个 1 像素高的 UIView,看起来像分隔线。

Be sure to turn on "bounces" and "bounce vertically" for the refresh control to work and so it seems more like a tableview.一定要打开“反弹”和“垂直反弹”以使刷新控件工作,因此它看起来更像是一个表格视图。

TableView shows some empty rows and dividers under your content, if it doesn't fill the full screen where as this solution doesn't. TableView 会在您的内容下显示一些空行和分隔符,如果它没有填满全屏,而这个解决方案没有。 But personally, I prefer if those empty rows weren't there anyway - with variable cell height it always looked "buggy" to me anyway to have the empty rows in there.但就我个人而言,我更喜欢那些空行不存在的情况 - 可变单元格高度在我看来总是“有问题”,在那里有空行。

Here's hoping some other programmer reads my post BEFORE wasting 20+ hours trying to figure it out with Table View in their own app.希望其他程序员在浪费 20 多个小时试图在他们自己的应用程序中使用 Table View 解决问题之前阅读我的帖子。 :) :)

tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

在此处输入图片说明

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.我不得不使用动态视图(通过代码设置视图和约束),当我想设置 preferredMaxLayoutWidth 标签的宽度为 0 时。所以我得到了错误的单元格高度。

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.在该标签的宽度符合预期并且动态高度计算正确之后。

Let's say you have a cell with a subview, and you want the cell's height to be high enough to encompass the subview + padding.假设您有一个带有子视图的单元格,并且您希望单元格的高度足够高以包含子视图 + 填充。

1) Set the subview's bottom constraint equal to the cell.contentView minus the padding you want. 1) 设置子视图的底部约束等于 cell.contentView 减去你想要的填充。 Do not set constraints on the cell or cell.contentView itself.不要对单元格或 cell.contentView 本身设置约束。

2) Set either the tableView's rowHeight property or tableView:heightForRowAtIndexPath: to UITableViewAutomaticDimension . 2) 将 tableView 的rowHeight属性或tableView:heightForRowAtIndexPath:UITableViewAutomaticDimension

3) Set either the tableView's estimatedRowHeight property or tableView:estimatedHeightForRowAtIndexPath: to a best guess of the height. 3)无论是设置的tableView的estimatedRowHeight财产或tableView:estimatedHeightForRowAtIndexPath:以高度的最佳猜测。

That's it.就是这样。

If you do you layout programmatically, here is what to consider for iOS 10 using anchors in Swift.如果您以编程方式进行布局,以下是在 Swift 中使用锚点的 iOS 10 需要考虑的事项。

There are three rules/ steps有三个规则/步骤

NUMBER 1: set this two properties of tableview on viewDidLoad, the first one is telling to the tableview that should expect dynamic sizes on their cells, the second one is just to let the app calculate the size of the scrollbar indicator, so it helps for performance. NUMBER 1:在 viewDidLoad 上设置 tableview 的这两个属性,第一个是告诉 tableview 应该期望其单元格上的动态大小,第二个只是让应用程序计算滚动条指示器的大小,因此它有助于表现。

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NUMBER 2: This is important you need to add the subviews to the contentView of the cell not to the view, and also use its layoutsmarginguide to anchor the subviews to the top and bottom, this is a working example of how to do it.数字 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)

        ])
}

Create a method that will add the subviews and perform the layout, call it in the init method.创建一个方法来添加子视图并执行布局,在 init 方法中调用它。

NUMBER 3: DON'T CALL THE METHOD:数字 3:不要调用方法:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    }

If you do it you will override your implementation.如果你这样做,你将覆盖你的实现。

Follow this 3 rules for dynamic cells in tableviews.在 tableviews 中遵循这 3 条动态单元格规则。

here is a working implementation https://github.com/jamesrochabrun/MinimalViewController这是一个有效的实现https://github.com/jamesrochabrun/MinimalViewController

If you have a long string.如果你有一个很长的字符串。 eg one which doesn't have a line break.例如一个没有换行符的。 Then you you might run into some problems.那么你可能会遇到一些问题。

The "alleged" fix is mentioned by the accepted answer and few other answers.接受的答案和其他几个答案都提到了“所谓的”修复。 You just need to add你只需要添加

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

I find Suragh's answer the most complete and concise , hence not confusing.我发现苏拉格的回答最完整和简洁,因此不会令人困惑。

Though non explain why these changes are needed.虽然没有解释为什么需要这些改变。 Let's do that.让我们这样做。

Drop the following code in to a project.将以下代码放入项目中。

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
    }
}

Note that I haven't added any size constraints.请注意,我没有添加任何大小限制。 I've only added centerX, centerY constraints.我只添加了 centerX、centerY 约束。 But still the label will be sized correctly Why?但是标签的尺寸仍然正确,为什么?

Because of contentSize .因为contentSize

To better process this, first keep step0, then comment out out steps 1-6.为了更好地处理这个问题,首先保留 step0,然后注释掉步骤 1-6。 Let setupLayout() stay.setupLayout()留下来。 Observe the behavior.观察行为。

Then uncomment step1, and observe.然后取消注释step1,并观察。

Then uncomment step2 and observe.然后取消注释 step2 并观察。

Do this until you've uncommented all 6 steps and observed their behaviors.这样做直到您取消了所有 6 个步骤的注释并观察了它们的行为。

What can conclude from all this?从这一切可以得出什么结论? What factors can change the contenSize ?哪些因素可以改变contenSize

  1. Text Length: If you have a longer text then your intrinsicContentSize's width will increase文本长度:如果你有更长的文本,那么你的内在内容大小的宽度会增加
  2. Line breaks: If you add \\n then the intrinsicContentSize's width will the maximum width of all lines.换行符:如果添加\\n则内在内容大小的宽度将是所有行的最大宽度。 If one line has 25 characters, another has 2 characters and another has 21 characters then your width will be calculated based the 25 characters如果一行有 25 个字符,另一行有 2 个字符,另一行有 21 个字符,那么您的宽度将根据 25 个字符计算
  3. Number of allowed lines: You must set the numberOfLines to 0 otherwise the you won't have multiple lines.允许的行数:您必须将numberOfLines设置为0否则您将不会有多行。 Your numberOfLines will adjust your intrinsicContentSize's height您的numberOfLines将调整您的内在内容大小的高度
  4. Making adjustments: Imagine that based on your text, your intrinsicContentSize's width was 200 and height was 100 , but you wanted to limited the width to the label's container what are you going to do?进行调整:想象一下,根据您的文本,您的内在内容大小的宽度为200 ,高度为100 ,但您想将宽度限制为标签的容器,您打算怎么做? The solution is to set it to a desired width.解决方案是将其设置为所需的宽度。 You do that by setting preferredMaxLayoutWidth to 130 then your new intrinsicContentSize will have a width of roughly 130 .您可以通过将preferredMaxLayoutWidth设置为130来做到这一点,然后您的新内在内容大小将具有大约130的宽度。 The height would obviously be more than 100 because you'd need more lines.高度显然会超过100因为您需要更多的线条。 That being said if your constraints are set correctly then you won't need to use this at all!话虽如此,如果您的约束设置正确,那么您根本不需要使用它! For more on that see this answer and its comments.有关更多信息,请参阅此答案及其评论。 You only need to use preferredMaxLayoutWidth if you don't have constraints restricting the width/height as in one might say "don't wrap the text unless it exceeds the preferredMaxLayoutWidth ".如果您没有限制宽度/高度的约束,您只需要使用preferredMaxLayoutWidth ,因为有人可能会说“除非文本超过preferredMaxLayoutWidth否则不要换行”。 But with 100% certainty if you set the leading/trailing and numberOfLines to 0 then you're good!但是,如果您将前导/尾随和numberOfLines0则可以 100% 确定,那么您就很好! Long story short most answers here which recommend using it are WRONG!长话短说,这里推荐使用它的大多数答案都是错误的! You don't need it.你不需要它。 Needing it is a sign that your constraints are not set correctly or that you just don't have constraints需要它表明您的约束设置不正确或您只是没有约束

  5. Font Size: Also note that if you increase your fontSize then the intrinsicContentSize's height will increase.字体大小:另请注意,如果您增加 fontSize,那么 internalContentSize 的高度将增加。 I didn't show that in my code.我没有在我的代码中显示这一点。 You can try that on your own.你可以自己试试。

So back to your tableViewCell example:所以回到你的 tableViewCell 示例:

All you need to do is:您需要做的就是:

  • set the numberOfLines to 0numberOfLines设置为0
  • constrain the label correctly to the margins/edges将标签正确约束到边距/边缘
  • There is no need to set preferredMaxLayoutWidth .无需设置preferredMaxLayoutWidth

In my case i have to create a custom cell with a image which is coming from server and can be of any width and height.在我的情况下,我必须创建一个带有来自服务器的图像的自定义单元格,并且可以是任何宽度和高度。 And two UILabels with dynamic size(both width & height)和两个具有动态大小(宽度和高度)的 UILabels

i have achieved the same here in my answer with autolayout and programmatically:我在我的回答中通过自动布局和以编程方式实现了同样的目标:

Basically above @smileyBorg answer helped but systemLayoutSizeFittingSize never worked for me, In my approach :基本上高于@smileyBorg 的回答有帮助,但 systemLayoutSizeFittingSize 从来没有为我工作过,在我的方法中:

1. No use of automatic row height calculation property. 1. 没有使用自动行高计算属性。 2.No use of estimated height 3.No need of unnecessary updateConstraints. 2. 不使用估计高度 3. 不需要不必要的更新约束。 4.No use of Automatic Preferred Max Layout Width. 4.不使用自动首选最大布局宽度。 5. No use of systemLayoutSizeFittingSize (should have use but not working for me, i dont know what it is doing internally), but instead my method -(float)getViewHeight working and i know what it's doing internally. 5. 不使用systemLayoutSizeFittingSize (应该有用但对我不起作用,我不知道它在内部做什么),而是我的方法 -(float)getViewHeight 工作,我知道它在内部做什么。

Is it possible to have differing heights in a UITableView Cell when I use several different ways of displaying the cell? 当我使用几种不同的单元格显示方式时,是否可以在 UITableView 单元格中具有不同的高度?

In my case, the padding was because of the sectionHeader and sectionFooter heights, where storyboard allowed me to change it to minimum 1. So in viewDidLoad method:在我的情况下,填充是因为 sectionHeader 和 sectionFooter 高度,故事板允许我将其更改为最小 1。所以在 viewDidLoad 方法中:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

I just did some dumb try and error with the 2 values of rowHeight and estimatedRowHeight and just thought it might provide some debugging insight:我只是做了一些愚蠢的尝试和错误使用的2个值rowHeightestimatedRowHeight ,只是认为它可能提供一些见解调试:

If you set them both OR only set the estimatedRowHeight you will get the desired behavior:如果您同时设置它们或仅设置estimatedRowHeight您将获得所需的行为:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

It's suggested that you do your best to get the correct estimate, but the end result isn't different.建议您尽最大努力获得正确的估计,但最终结果并没有什么不同。 It will just affect your performance.它只会影响你的表现。

在此处输入图片说明


If you only set the rowHeight ie only do:如果你只设置 rowHeight 即只做:

tableView.rowHeight = UITableViewAutomaticDimension

your end result would not be as desired:你的最终结果不会如你所愿:

在此处输入图片说明


If you set the estimatedRowHeight to 1 or smaller then you will crash regardless of the rowHeight .如果设置estimatedRowHeight为1或更小,那么你会不管的崩溃rowHeight

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

I crashed with the following error message:我因以下错误消息而崩溃:

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

With regard to the accepted answer by @smileyborg, I have found关于@smileyborg 接受的答案,我发现

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

to be unreliable in some cases where constraints are ambiguous.在某些约束不明确的情况下不可靠。 Better to force the layout engine to calculate the height in one direction, by using the helper category on UIView below:通过使用下面 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;
}

Where w: is the width of the tableview其中 w: 是 tableview 的宽度

Simply add these two functions in your viewcontroller it will solve your problem.只需在您的视图控制器中添加这两个函数即可解决您的问题。 Here, list is a string array which contain your string of every row.在这里, 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
}
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)

    }

If the cell height is dynamic by the content, you should precisely count it out and then return the height value before the cell is rendered.如果单元格高度是由内容动态变化的,您应该精确地将其计算出来,然后在呈现单元格之前返回高度值。 An easy way is to define the counting method in the table view cell code for controller to call at the table cell height delegate method.一个简单的方法是在表格视图单元格代码中定义计数方法,以便控制器在表格单元格高度委托方法时调用。 Don't forget to count out the real cell frame width (default is 320) if the height is rely on the width of the table or screen.如果高度依赖于表格或屏幕的宽度,请不要忘记计算实际单元格框架宽度(默认为 320)。 That is, in the table cell height delegate method, use cell.frame to correct the cell width first, then call the counting height method defined in the cell to get the suitable value and return it .在表格单元格高度委托方法中,先使用cell.frame对单元格宽度进行修正,然后调用单元格中定义的计数高度方法获取合适的值并返回

PS.附注。 The code for generating cell object could be defined in another method for different table view cell delegate method to call.生成单元格对象的代码可以定义在另一种方法中,供不同的表视图单元格委托方法调用。

UITableView.automaticDimension can be set via Interface Builder: UITableView.automaticDimension可以通过 Interface Builder 设置:

Xcode > Storyboard > Size Inspector Xcode > 故事板 > 尺寸检查器

Table View Cell > Row Height > Automatic表格视图单元格 > 行高 >自动

尺寸检查员

yet another iOs7+iOs8 solution in Swift 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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM