如何在表视图中的UITableViewCells中使用“自动布局”,让每个单元格的内容和子视图确定行高度(自身/自动),同时保持平滑的滚动性能?


当前回答

关于@smileyborg接受的答案,我发现

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

在某些约束不明确的情况下是不可靠的。最好使用下面UIView上的帮助器类别强制布局引擎计算一个方向上的高度:

-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
    [self setNeedsLayout];
    [self layoutIfNeeded];
    CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGFloat h = size.height;
    return h;
}

其中w:是表视图的宽度

其他回答

对于上面的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
}

但对于iOS 7,关键是计算自动布局后的高度:

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height
}

重要的

如果有多行标签,请不要忘记将numberOfLines设置为0。不要忘记label.preferredMaxLayoutWidth=CGRectGetWidth(tableView.bounds)

完整的示例代码在这里。

可变高度UITableViewCell的快速示例

为Swift 3更新

胡威廉(William Hu)的快速回答很好,但在我第一次学习做某事时,它能帮助我掌握一些简单而详细的步骤。下面的示例是我在学习使用可变单元格高度制作UITableView时的测试项目。我基于Swift的这个基本UITableView示例。

完成的项目应如下所示:

创建新项目

它可以只是一个单视图应用程序。

添加代码

将新的Swift文件添加到项目中。将其命名为MyCustomCell。此类将保存添加到情节提要中单元格的视图的出口。在这个基本示例中,每个单元格中只有一个标签。

import UIKit
class MyCustomCell: UITableViewCell {
    @IBOutlet weak var myCellLabel: UILabel!
}

我们稍后将连接此插座。

打开ViewController.swift并确保您具有以下内容:

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // These strings will be the data for the table view cells
    let animals: [String] = [
        "Ten horses:  horse horse horse horse horse horse horse horse horse horse ",
        "Three cows:  cow, cow, cow",
        "One camel:  camel",
        "Ninety-nine sheep:  sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
        "Thirty goats:  goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]

    // Don't forget to enter this in IB also
    let cellReuseIdentifier = "cell"

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // delegate and data source
        tableView.delegate = self
        tableView.dataSource = self

        // Along with auto layout, these are the keys for enabling variable cell height
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
    }

    // number of rows in table view
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.animals.count
    }

    // create a cell for each table view row
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.animals[indexPath.row]
        return cell
    }

    // method to run when table view cell is tapped
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}

重要说明:

以下两行代码(以及自动布局)使可变单元格高度成为可能:tableView.estimatedRowHeight=44.0tableView.rowHeight=UITableViewAutomaticDimension

设置情节提要

将表视图添加到视图控制器中,并使用自动布局将其固定到四边。然后将表视图单元格拖到表视图上。然后将标签拖到原型单元格上。使用自动布局将标签固定到表视图单元的内容视图的四个边缘。

重要说明:

自动布局与我上面提到的两行重要代码一起工作。如果不使用自动布局,它将无法工作。

其他IB设置

自定义类名和标识符

选择TableViewCell并将自定义类设置为MyCustomCell(我们添加的Swift文件中的类名称)。还将Identifier设置为cell(与上面代码中用于cellReuseIdentifier的字符串相同)。

标签的零行

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

连接插座

控件从情节提要中的表视图拖动到ViewController代码中的tableView变量。对Prototype单元格中的Label和MyCustomCell类中的myCellLabel变量执行同样的操作。

完成了

您现在应该能够运行项目并获得高度可变的单元格。

笔记

此示例仅适用于iOS 8及更高版本。如果您仍然需要支持iOS 7,那么这将不适用于您。未来项目中您自己的自定义单元格可能不止一个标签。确保将所有内容固定正确,以便自动布局可以确定要使用的正确高度。你可能还需要使用垂直抗压和拥抱。有关这方面的更多信息,请参阅本文。如果不固定前缘和后缘(左侧和右侧),则可能还需要设置标签的preferredMaxLayoutWidth,以便它知道何时换行。例如,如果在上面的项目中向标签添加了“水平居中”约束,而不是固定前缘和后缘,则需要将此行添加到tableView:cellForRowAtIndexPath方法:cell.myCellLabel.referencedMaxLayoutWidth=表格视图边界宽度

另请参见

了解iOS 8中的自调整单元格和动态类型具有不同行高的表视图单元格Swift的UITableView示例

在我的例子中,填充是因为sectionHeader和sectionFooter的高度,故事板允许我将其更改为最小值1。因此,在viewDidLoad方法中:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

如果单元格高度根据内容是动态的,则应精确计算出它,然后在渲染单元格之前返回高度值。一种简单的方法是在表视图单元格代码中定义计数方法,以便控制器在表单元格高度委托方法处调用。如果高度取决于表格或屏幕的宽度,请不要忘记计算实际单元格框架宽度(默认值为320)。也就是说,在表单元格高度委托方法中,首先使用cell.frame来校正单元格宽度,然后调用单元格中定义的计数高度方法来获得合适的值并返回它。

PS。可以在另一个方法中定义用于生成单元格对象的代码,以便调用不同的表视图单元格委托方法。

动态表视图单元格高度和自动布局

解决故事板自动布局问题的好方法:

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