在UITableView中使用自动布局以获取动态单元格布局和可变的行高


1501

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


这是swift 2.3中的示例代码github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

为了了解最问候的答案具有多的UILabel,你需要的是如何充分认识contentSizepreferredMaxLayoutWidth工作。看这里。话虽这么说,如果您正确设置了约束,那么您就不需要了preferredMaxLayoutWidth,实际上这样做会产生意想不到的结果。
亲爱的

Answers:


2386

TL; DR:不喜欢阅读?直接跳转到GitHub上的示例项目:

概念描述

无论您针对哪个iOS版本开发,以下前两个步骤均适用。

1.设置和添加约束

UITableViewCell子类中,添加约束,以使单元格的子视图的边缘固定到单元格的contentView的边缘(最重要的是固定到顶部和底部边缘)。注意:不要将子视图固定到单元格本身;只对细胞的contentView让这些子视图的固有内容大小驱动表格视图单元格的内容视图的高度,方法是确保每个子视图的垂直方向的内容压缩阻力内容拥抱约束都不会被添加的更高优先级约束所覆盖。(嗯?点击这里。

请记住,其想法是使单元的子视图垂直连接到单元的内容视图,以便它们可以“施加压力”并使内容视图扩展以适合它们。使用带有几个子视图的示例单元格,这是一些 (不是全部!)约束看起来像的视觉插图:

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

您可以想象,随着在上面的示例单元格中将更多文本添加到多行主体标签中,它将需要垂直增长以适合文本,这将有效地迫使单元格高度增加。(当然,您需要正确设置约束条件才能使其正常工作!)

正确使用约束绝对是使用自动版式获得动态像元高度时最困难也是最重要的部分。如果您在此处输入错误,则可能会阻止其他所有功能运行-因此,请花点时间!我建议您在代码中设置约束,因为您确切知道要在哪里添加约束,并且在出现问题时调试起来要容易得多。与使用布局锚点或GitHub上一种出色的开源API之一的Interface Builder相比,在代码中添加约束既简单又功能强大。

  • 如果要在代码中添加约束,则应在updateConstraintsUITableViewCell子类的方法中执行一次。请注意,它updateConstraints可能会被多次调用,因此为避免多次添加相同的约束,请确保将添加约束的代码包装updateConstraints在检查boolean属性中,例如didSetupConstraints(在运行约束后将其设置为YES) -一次添加代码)。另一方面,如果您有更新现有约束的代码(例如constant在某些约束上调整属性),请将其放在updateConstraints检查对象的外部,didSetupConstraints以便每次调用该方法时都可以运行。

2.确定唯一的表视图单元重用标识符

对于单元中的每个唯一约束集,请使用唯一的单元重用标识符。换句话说,如果您的单元具有多个唯一的布局,则每个唯一的布局应收到其自己的重用标识符。(当单元格变体具有不同数量的子视图,或者子视图以不同的方式排列时,就需要使用新的重用标识符。)

例如,如果您在每个单元格中显示一封电子邮件,则可能有4种独特的布局:仅包含主题的消息,包含主题和正文的消息,具有主题和照片附件的消息以及具有主题的消息,机身和照片附件。每个布局具有实现它所需的完全不同的约束,因此,一旦初始化了单元并为这些单元类型之一添加了约束,该单元就应获得特定于该单元类型的唯一重用标识符。这意味着当您使单元出队以供重用时,约束已被添加并且可以用于该单元类型。

请注意,由于内部内容大小的不同,具有相同约束(类型)的单元格可能仍具有不同的高度!不要将根本不同的布局(不同的约束)与由于内容大小不同而计算出的不同视图框架(由相同约束解决)混淆。

  • 不要在完全相同的重用池中添加具有完全不同的约束集的单元(即使用相同的重用标识符),然后在每次出队后尝试删除旧约束并从头开始设置新约束。内部的自动版式引擎并非旨在处理约束的大规模变化,您将看到大量的性能问题。

对于iOS 8-自定义单元格

3.启用行高估算

要启用自动调整大小的表格视图单元格,必须将表格视图的rowHeight属性设置为UITableViewAutomaticDimension。您还必须将一个值分配给estimatedRowHeight属性。一旦设置了这两个属性,系统就会使用“自动布局”来计算行的实际高度

苹果:使用自动调整大小的表格视图单元

在iOS 8中,Apple内部化了以前必须由iOS 8之前的用户执行的大部分工作。为了使自动调整单元格功能起作用,必须首先将rowHeight表视图上的属性设置为常量UITableViewAutomaticDimension。然后,您只需要通过将表视图的estimatedRowHeight属性设置为非零值来启用行高估计,例如:

self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is

这是为表格视图提供临时估计/占位符,用于尚未显示在屏幕上的单元格的行高。然后,当这些单元格即将在屏幕上滚动时,将计算实际的行高。为了确定每一行的实际高度,表格视图contentView会根据内容视图的已知固定宽度(基于表格视图的宽度,减去任何其他内容,例如节索引)自动询问每个单元格需要达到的高度或附件视图)以及您已添加到单元格内容视图和子视图的自动布局约束。确定此实际单元格高度后,将使用新的实际高度更新该行的旧估计高度(并根据需要对表视图的contentSize / contentOffset进行任何调整)。

一般而言,您提供的估算值不一定非常准确-仅用于在表格视图中正确调整滚动指示器的大小,而表格视图在您调整滚动指示器以适应不正确的估算值方面做得很好滚动屏幕上的单元格。您应该将estimatedRowHeight表视图上的属性(以viewDidLoad类似方式)设置为恒定值,即“平均”行高。仅当您的行高具有极大的可变性(例如,相差一个数量级)并且在滚动时注意到滚动指示器“跳跃”时,您tableView:estimatedHeightForRowAtIndexPath:才需要执行最小化计算来为每行返回更准确的估计值。

对于iOS 7支持(自行实现自动调整大小)

3.进行布局通过并获取像元高度

首先,实例化一个表格视图单元格的屏幕外实例,每个重用标识符一个实例该实例严格用于高度计算。(屏幕外表示单元格引用存储在视图控制器上的属性/ ivar中,从不返回tableView:cellForRowAtIndexPath:以使表格视图实际在屏幕上呈现。)接下来,必须为单元格配置准确的内容(例如,文本,图像等)如果要在表格视图中显示,它将保留。

然后,迫使细胞立即布局其子视图,然后使用systemLayoutSizeFittingSize:该方法UITableViewCellcontentView找出电池的必要高度是什么。使用UILayoutFittingCompressedSize获得的最小尺寸必须适应单元格中的所有内容。然后可以从tableView:heightForRowAtIndexPath:委托方法中返回高度。

4.使用估计的行高

如果您的表格视图中包含多于几十行,您会发现执行“自动布局”约束求解可以在首次加载表格视图时迅速使主线程陷入困境,就像tableView:heightForRowAtIndexPath:在第一次加载时在每一行上调用的那样(以计算滚动指示器的大小)。

从iOS 7开始,您可以(绝对应该)estimatedRowHeight在表格视图上使用属性。这是为表格视图提供临时估计/占位符,用于尚未显示在屏幕上的单元格的行高。然后,当这些单元格即将在屏幕上滚动时,将计算实际行高(通过调用tableView:heightForRowAtIndexPath:),并将估计的高度更新为实际行高。

一般而言,您提供的估算值不一定非常准确-仅用于在表格视图中正确调整滚动指示器的大小,而表格视图在您调整滚动指示器以适应不正确的估算值方面做得很好滚动屏幕上的单元格。您应该将estimatedRowHeight表视图上的属性(以viewDidLoad类似方式)设置为恒定值,即“平均”行高。仅当您的行高具有极大的可变性(例如,相差一个数量级)并且在滚动时注意到滚动指示器“跳跃”时,您tableView:estimatedHeightForRowAtIndexPath:才需要执行最小化计算来为每行返回更准确的估计值。

5.(如果需要)添加行高缓存

如果您已完成上述所有操作,但在中进行约束求解时仍然发现性能降低得令人无法接受tableView:heightForRowAtIndexPath:,那么很遗憾,您需要为单元格高度实现一些缓存。(这是Apple工程师建议的方法。)一般的想法是让Autolayout引擎第一次解决约束,然后缓存该单元格的计算高度,并将缓存的值用于该单元格高度的所有将来请求。当然,技巧是确保在发生任何可能导致单元格高度发生变化的事件时清除单元格的缓存高度-主要是,这是在该单元格的内容发生更改或其他重要事件发生时(例如用户调整动态类型文字大小滑块)。

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

样本项目

这些项目是具有可变行高的表格视图的完全可用示例,这是由于表格视图单元格包含UILabels中的动态内容。

Xamarin(C#/。NET)

如果您使用的是Xamarin,请查看@KentBoogaart组合的此示例项目


1
尽管效果很好,但我发现它略微估算了所需的大小(可能是由于各种取整问题造成的),我必须在最终高度上加几个点,以便所有文字都适合标签内
DBD

5
@ Alex311非常有趣,感谢您提供了此示例。我做了一些在我结束测试,并在这里写了一些意见:github.com/Alex311/TableCellWithAutoLayout/commit/...
smileyborg

3
我强烈建议为每种单元重用标识符类型缓存单元。每次您从高度中出队时,您都会将一个单元从队列中移出,而该单元没有被添加回去。缓存可以显着降低表单元初始化程序的调用时间。由于某种原因,当我不缓存时,正在使用的内存量一直在增长,就像我滚动一样。
Alex311 2013年

1
故事板,实际上。我认为这不适用于原型单元(至少不重新实例化整个VC)。通过使中出队heightForRowAtIndexPath,保留单元格并在下次cellForRowAtIndexPath调用时返回它,可以完全避免泄漏。
nschum 2014年

2
自发布此答案以来,是否iOS8仍建议对iOS9和实施iOS10,还是有新方法?
koen

175

对于上面的iOS 8,它非常简单:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableView.automaticDimension
}

要么

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
}

重要

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

  • 别忘了 label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

完整的示例代码在这里


7
我认为我们不需要在OS8案例中实现heightForRowAtIndexPath函数
jpulikkottil,2015年

正如@eddwinpaz在另一个答案上指出的那样,重要的是用于锁定行高的约束不包括边距。
理查德(Richard)

1
为“如果有多行标签,别忘了将numberOfLines设置为0” +1,这导致我的单元格大小不是动态的。
伊恩·福格曼

称我为控制狂,但即使在iOS 11时代,我仍然不喜欢使用UITableViewAutomaticDimension。过去有一些不好的经验。因此,我通常使用此处列出的iOS7解决方案。威廉的注意事项不要忘记label.preferredMaxLayoutWidth在这里救了我。
Brian Sachetta

当我们滚动时,标签开始以多行显示,行的高度也没有增加
Karanveer Singh,

93

可变高度UITableViewCell的Swift示例

为Swift 3更新

William Hu的Swift回答很好,但是当我第一次学习做某事时,它可以帮助我采取一些简单而详细的步骤。下面的示例是我的学习项目,同时学习了如何制作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.0
    tableView.rowHeight = UITableViewAutomaticDimension

设置情节提要

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

在此处输入图片说明

重要的提示:

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

其他IB设置

自定义类名称和标识符

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

在此处输入图片说明

标签零线

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

在此处输入图片说明

连接插座

  • 控制从情节提要中的“表视图”拖动到代码中的tableView变量ViewController
  • 对Prototype单元格中的Label myCellLabel对该MyCustomCell类中的变量执行相同的操作。

已完成

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

笔记

  • 此示例仅适用于iOS 8及更高版本。如果您仍然需要支持iOS 7,则此功能将对您不起作用。
  • 在未来的项目中,您自己的自定义单元可能会包含多个标签。确保正确固定所有内容,以便自动布局可以确定要使用的正确高度。您可能还必须使用垂直压缩阻力和抱紧力。有关更多信息,请参见本文
  • 如果不固定前缘和后缘(左右),则可能还需要设置标签的标签,preferredMaxLayoutWidth以便知道何时换行。例如,如果您在上面的项目中向标签添加了“水平居中”约束,而不是固定前缘和后缘,则需要将此行添加到tableView:cellForRowAtIndexPath方法中:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

也可以看看


preferredMaxLayoutWidth对于单元格设置它实际上不是更好 contentSize吗?这样,如果您有一个AccessoriesView或已对其进行刷卡进行编辑,那么仍将其考虑在内?
亲爱的

@蜂蜜,你可能是正确的。我已经一年没有跟上iOS了,我太生锈了,无法立即回答您。
苏拉奇

65

我将@smileyborg的iOS7解决方案包装在一个类别中

我决定将@smileyborg的巧妙解决方案包装到一个UICollectionViewCell+AutoLayoutDynamicHeightCalculation类别中。

该类别还纠正了@wildmonkey的答案中概述的问题(从笔尖加载细胞并systemLayoutSizeFittingSize:返回CGRectZero

它不考虑任何缓存,但是现在适合我的需求。随意复制,粘贴和修改。

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

#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;
}

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
                                                                     attribute:constraint.firstAttribute
                                                                     relatedBy:constraint.relation
                                                                        toItem:secondItem
                                                                     attribute:constraint.secondAttribute
                                                                    multiplier:constraint.multiplier
                                                                      constant:constraint.constant]];
    }];
}

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
    NSParameterAssert(block);

    block();

    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;

}

@end

用法示例:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

值得庆幸的是,我们不必在iOS8中进行爵士乐演奏,但是现在就可以了!


2
您应该只可以简单地使用a:[YourCell new]并将其用作虚拟对象。只要在您的实例中触发了约束代码构建代码,并且您以编程方式触发了布局遍历,您就应该很好。
亚当·怀特

1
谢谢!这可行。您的类别很棒。是什么让我意识到这种技术也适用UICollectionViews
里卡多·桑切斯·塞兹

2
您将如何使用情节提要中定义的原型单元来执行此操作?
大卫·波特

57

这是我的解决方案:

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

目标C

- (void)viewWillAppear:(BOOL)animated {
    _messageField.delegate = self;
    _tableView.estimatedRowHeight = 65.0;
    _tableView.rowHeight = UITableViewAutomaticDimension;
}

更新到Swift 4.2

override func viewWillAppear(_ animated: Bool) {
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 65.0
}

2
正确设置自动布局,并在viewDidLoad中添加此代码,就可以解决问题。
2015年

1
但是如果estimatedRowHeight逐行变化怎么办?我应该超出还是低于估计?使用我使用的高度的最小值或最大值tableView
亚诺什

1
@János这是rowHeight的要点。若要做到这一点,您需要使用没有边距的约束并将对象与TableViewCell对齐。并且我假设您使用的是UITextView,因此仍然需要删除autoscroll = false,否则它将保持高度,而相对高度将无法按预期运行。
Eddwin Paz 2015年

1
这是迄今为止最可靠的解决方案。不用担心estimatedRowHeight,它主要影响滚动条的大小,而不影响实际单元格的高度。选择的高度要大胆:这会影响插入/删除动画。
SwiftArchitect

这是swift 2.3中的示例代码github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

47

@smileyborg提出的解决方案几乎是完美的。如果您有一个自定义单元格,并且希望一个或多个UILabel具有动态高度,则systemLayoutSizeFittingSize方法与启用了AutoLayout的结合将返回a,CGSizeZero除非您将所有单元格约束从该单元格移至其contentView(如@TomSwift所建议,此处如何将 Superview 调整为使所有子视图都适合自动布局吗?)。

为此,您需要在自定义UITableViewCell实现中插入以下代码(感谢@Adrian)。

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

将@smileyborg答案与此混用即可。


systemLayoutSizeFittingSize需要在contentView而不是单元格上调用
onmyway133

25

我刚遇到要发布的答案就足够重要。

@smileyborg的答案基本上是正确的。但是,如果您layoutSubviews的自定义单元格类的方法中包含任何代码(例如设置)preferredMaxLayoutWidth,则它将不会与此代码一起运行:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

这让我有些困惑。然后我意识到这是因为这些仅触发而contentView不是单元格本身的layoutSubviews 。

我的工作代码如下所示:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;

请注意,如果您要创建一个新的单元格,我很确定您不需要调用setNeedsLayout它,因为它应该已经设置了。如果您保存对单元格的引用,则可能应该调用它。无论哪种方式,它都不会伤害任何东西。

如果您使用的是单元格子类,则另外一个技巧是设置preferredMaxLayoutWidth。正如@smileyborg提到的,“您的表格视图单元格的宽度尚未固定为表格视图的宽度”。的确如此,如果您在子类中而不是在视图控制器中进行工作,那么会很麻烦。但是,您可以在此时使用表格宽度简单地设置单元格框架:

例如在高度计算中:

self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);

(我碰巧要缓存此特定单元以供重复使用,但这无关紧要)。



19

只要您在单元格中的布局良好即可。

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

更新:您应该使用iOS 8中引入的动态调整大小。


这是为我工作的iOS7,是现在确定打电话tableView:cellForRowAtIndexPath:tableView:heightForRowAtIndexPath:呢?
蚂蚁2014年

好了,所以这是行不通的,但是当我打电话systemLayoutSizeFittingSize:tableView:cellForRowAtIndexPath:和缓存结果,然后再使用,在tableView:heightForRowAtIndexPath:它工作得很好,只要约束条件是设置正确,当然!
蚂蚁2014年

仅当您使用dequeueReusableCellWithIdentifier:而不是dequeueReusableCellWithIdentifier:forIndexPath时,此方法才有效:
Antoine

1
我真的不认为直接调用tableView:cellForRowAtIndexPath:是一个好方法。
Itachi

19

(对于Xcode 8.x / Xcode 9.x在底部读取)

当心Xcode 7.x中的以下问题,这可能会引起混乱:

Interface Builder无法正确处理自动调整单元设置的大小。即使您的约束条件绝对有效,IB仍会抱怨并给您令人困惑的建议和错误。原因是IB不愿意按照您的约束条件更改行的高度(以使单元格适合您的内容)。相反,它使行的高度保持固定,并开始建议您更改约束,您应该忽略该约束。

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

在此处输入图片说明

现在,如果您更改字体大小(在本示例中,我将描述标签字体大小从17.0更改为18.0)。

在此处输入图片说明

因为字体大小增加了,所以标签现在要占用3行(在占用2行之前)。

如果Interface Builder能够按预期工作,它将调整单元格的高度大小以适应新的标签高度。但是,实际情况是IB显示红色的自动布局错误图标,并建议您修改拥抱/压缩优先级。

在此处输入图片说明

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

在此处输入图片说明

我一次(使用向上/向下步进器)一次更改此高度,直到红色箭头错误消失!(实际上,您会收到黄色警告,然后继续执行“更新框架”,一切都应该可以)。

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

实际上,尝试始终在IB中更新行高非常烦人,有时几乎是不可能的(由于小数值)。

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

在此处输入图片说明


Xcode 8.x / Xcode 9.x似乎(有时)在做事上与Xcode 7.x不同,但仍然是错误的。例如,即使将compression resistance priority/ hugging priority设置为required(1000),Interface Builder 也会拉伸或剪切标签以适合单元格(而不是调整单元格高度以适合标签周围)。在这种情况下,它甚至可能不会显示任何AutoLayout警告或错误。或者有时它确实可以完成Xcode 7.x的操作,如上所述。


您好,可以为单元格提供动态高度,即具有具有动态单元格内容的单元格的tableview吗?
Jignesh B

18

要为行高和估计的行高设置自动尺寸,请确保执行以下步骤,自动尺寸对单元格/行高布局有效。

  • 分配并实现tableview数据源和委托
  • 分配UITableViewAutomaticDimension给rowHeight和估计的RowHeight
  • 实现委托/数据源方法(即向其heightForRowAt返回值UITableViewAutomaticDimension

--

目标C:

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

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

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

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

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

    return UITableViewAutomaticDimension;
}

迅速:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

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

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


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

}



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

    // Swift 4.1 and below
    return UITableViewAutomaticDimension
}

对于UITableviewCell中的标签实例

  • 设置行数= 0(&换行模式=截尾)
  • 设置关于其superview /单元容器的所有约束(上,下,左左)。
  • 可选:即使没有数据,如果您希望标签覆盖最小的垂直区域,请设置标签的最小高度。

在此处输入图片说明

注意:如果您有多个具有动态长度的标签(UIElements),则应根据其内容大小进行调整:对于要以更高优先级进行扩展/压缩的标签,请调整“内容拥抱和抗压缩优先级”。


1
谢谢似乎是一个简单明了的解决方案,但是我有一个问题,我不是在使用标签,而是在使用textviews,因此我需要在添加数据时增加行高。然后我的问题是将信息传递给heightForRowAt。我可以测量更改的文本视图的高度,但现在需要更改行高。希望得到一些帮助
杰里米·安德鲁斯

@JeremyAndrews当然可以帮助您。使用您尝试过的源代码和有关问题的详细信息来提出您的问题。
克鲁纳尔

它对我tableView: heightForRow有用,但没有实现数据源。
Hemang '18年

@Hemang-自iOS 11+起,无需运行tableView: heightForRow。(对于iOS 10- tableView: heightForRow是必需的)
Krunal '18

1
@Hemang-解决方案取决于确切的查询定义。在这里,我为您的查询提供了通用解决方案。把条件放进去tableView: heightForRow..if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
克鲁纳尔18'Oct

16

@ Bob-Spryn一样,我遇到了一个非常重要的陷阱,因此我将其发布为答案。

我在@smileyborg的答案中挣扎了一段时间。我遇到的问题是,如果您在IB中实例化单元时在IB中用其他元素(UILabelsUIButtons等)定义了原型单元,[YourTableViewCellClass alloc] init]除非您已经实例化了该单元中的所有其他元素。编写代码来做到这一点。(我对也有类似的经验initWithStyle。)

要使情节提要板实例化,所有其他元素都将用实例化[tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"](不是,[tableView dequeueReusableCellWithIdentifier:forIndexPath:]因为这会引起有趣的问题。)执行此操作时,将实例化IB中定义的所有元素。


15

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

解决情节提要自动布局的一个好方法:

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

1
对此问题的可接受答案已经对此进行了广泛讨论。
smileyborg 2014年

2
是的,我知道...但是我不想使用PureLayout,而dispatch_once'trick'帮了我很多忙,仅使用Storyboard就能解决问题。
Mohnasm 2014年

13
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

在此处输入图片说明


13

另一个“解决方案”:跳过所有这些麻烦,而改用UIScrollView来获得外观和感觉与UITableView相同的结果。

这对我来说是一个痛苦的“解决方案”,因为他花了将近20多个非常令人沮丧的小时来尝试构建类似Smileyborg所建议的内容,并且使App Store发行了三个月的多个版本而失败。

我的观点是,如果您确实需要iOS 7支持(对我们来说,这是必不可少的),那么该技术太脆弱了,您将无法尝试。而且,除非您正在使用某些高级行编辑功能和/或确实需要支持1000多个“行”(在我们的应用程序中,实际上不超过20行),否则UITableView完全是矫kill过正。

额外的好处是,与UITableView附带的所有委托来回相比,代码变得异常简单。这只是viewOnLoad中的一个代码循环,看起来很优雅并且易于管理。

以下是有关操作方法的提示:

  1. 使用Storyboard或nib文件,创建一个ViewController和关联的根视图。

  2. 将UIScrollView拖到根视图上。

  3. 将约束顶部,底部,左侧和右侧的约束添加到顶层视图,以便UIScrollView填充整个根视图。

  4. 在UIScrollView内添加一个UIView并将其称为“容器”。向UIScrollView(其父级)添加顶部,底部,左侧和右侧约束。关键技巧:还添加“等宽”约束以链接UIScrollView和UIView。

    注意:您将收到一个错误“滚动视图具有可滚动的内容高度不明确的高度”,并且您的容器UIView的高度应为0像素。应用程序运行时,这两个错误似乎都无关紧要。

  5. 为每个“单元”创建nib文件和控制器。使用UIView而不是UITableViewCell。

  6. 在您的根ViewController中,您实际上是将所有“行”添加到容器UIView中,并以编程方式添加约束,将其左右边缘链接到容器视图,将它们的上边缘链接到容器视图的顶部(对于第一项)或上一个细胞。然后将最后一个单元格链接到容器底部。

对于我们来说,每个“行”都位于一个nib文件中。所以代码看起来像这样:

class YourRootViewController {

    @IBOutlet var container: UIView! //container mentioned in step 4

    override func viewDidLoad() {

        super.viewDidLoad()

        var lastView: UIView?
        for data in yourDataSource {

            var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
            UITools.addViewToTop(container, child: cell.view, sibling: lastView)
            lastView = cell.view
            //Insert code here to populate your cell
        }

        if(lastView != nil) {
            container.addConstraint(NSLayoutConstraint(
                item: lastView!,
                attribute: NSLayoutAttribute.Bottom,
                relatedBy: NSLayoutRelation.Equal,
                toItem: container,
                attribute: NSLayoutAttribute.Bottom,
                multiplier: 1,
                constant: 0))
        }

        ///Add a refresh control, if you want - it seems to work fine in our app:
        var refreshControl = UIRefreshControl()
        container.addSubview(refreshControl!)
    }
}

这是UITools.addViewToTop的代码:

class UITools {
    ///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
    class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
    {
        child.setTranslatesAutoresizingMaskIntoConstraints(false)
        container.addSubview(child)

        //Set left and right constraints so fills full horz width:

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Leading,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Left,
            multiplier: 1,
            constant: 0))

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Trailing,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Right,
            multiplier: 1,
            constant: 0))

        //Set vertical position from last item (or for first, from the superview):
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Top,
            relatedBy: NSLayoutRelation.Equal,
            toItem: sibling == nil ? container : sibling,
            attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
            multiplier: 1,
            constant: 0))
    }
}

到目前为止,我用这种方法发现的唯一“陷阱”是UITableView在滚动时在视图顶部具有“浮动”节标题的漂亮功能。除非您添加更多的编程,否则上述解决方案将不会做到这一点,但是对于我们的特殊情况,此功能并不是100%必不可少的,没有人注意到它何时消失。

如果要在单元格之间使用分隔线,只需在自定义“单元格”底部添加一个1像素高的UIView,它看起来像分隔线。

请确保打开“反弹”和“垂直反弹”,刷新控件才能起作用,因此它看起来更像是一个表格视图。

TableView在您的内容下显示了一些空行和分隔符,如果它不能填满全屏,则此解决方案无法解决。但就我个人而言,我更喜欢如果这些空行根本不存在-单元格高度可变的话,无论如何我总是觉得其中有空行。

希望其他程序员在浪费20多个小时才能尝试在自己的应用程序中使用Table View弄清楚之前阅读我的文章。:)


11

我必须使用动态视图(按代码设置视图和约束),并且当我想将preferredMaxLayoutWidth标签的宽度设置为0时,所以单元格高度错误。

然后我加了

[cell layoutSubviews];

执行之前

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

之后,标签的宽度符合预期,动态高度正在正确计算。


9

假设您有一个带有子视图的单元格,并且希望该单元格的高度足够高以包含子视图+填充。

1)设置子视图的底部约束等于cell.contentView减去所需的填充。不要在单元格或cell.contentView本身上设置约束。

2)将tableView的rowHeight属性设置tableView:heightForRowAtIndexPath:UITableViewAutomaticDimension

3)设置tableView的estimatedRowHeight属性或tableView:estimatedHeightForRowAtIndexPath:对高度的最佳猜测。

而已。


7

如果您以编程方式进行布局,则以下是在Swift中使用锚点的iOS 10的考虑因素。

共有三个规则/步骤

NUMBER 1:在viewDidLoad上设置tableview的这两个属性,第一个告诉tableview应该期望其单元格上的动态大小,第二个只是让应用程序计算滚动条指示器的大小,因此有助于性能。

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

第2点:这很重要,您需要将子视图添加到单元格的contentView中而不是添加到视图中,还需要使用其layoutsmarginguide将子视图锚定到顶部和底部,这是如何执行此操作的示例。

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setUpViews()
}

private func setUpViews() {

    contentView.addSubview(movieImageView)
    contentView.addSubview(descriptionLabel)
    let marginGuide = contentView.layoutMarginsGuide

    NSLayoutConstraint.activate([
        movieImageView.heightAnchor.constraint(equalToConstant: 80),
        movieImageView.widthAnchor.constraint(equalToConstant: 80),
        movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
        movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),

        descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
        descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
        descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
        descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)

        ])
}

创建一个将添加子视图并执行布局的方法,在init方法中调用它。

第3点:请勿拨打电话:

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

如果这样做,您将覆盖实现。

对于表视图中的动态单元格,请遵循以下3条规则。

这是一个有效的实现 https://github.com/jamesrochabrun/MinimalViewController


在Swift 5中,UITableViewAutomaticDimension重命名为UITableView.automaticDimension
James Rochabrun

4

如果您的字符串很长。例如,没有换行符。然后,您可能会遇到一些问题。

接受的答案和其他几个答案都提到了“已通知”的修复程序。您只需要添加

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

我认为苏拉(Suragh)的答案最完整,最简洁,因此不会造成混淆。

虽然没有解释为什么需要这些更改。来做吧。

将以下代码放入项目中。

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

请注意,我尚未添加任何尺寸限制。我只添加了centerX,centerY约束。但是仍然可以正确调整标签尺寸,为什么?

因为contentSize

为了更好地处理此问题,请首先保留step0,然后注释掉步骤1-6。让setupLayout()时光。观察行为。

然后取消注释步骤1,并观察。

然后取消注释步骤2并观察。

这样做直到您取消注释所有6个步骤并观察它们的行为。

从这一切中可以得出什么结论?哪些因素可以改变contenSize

  1. 文字长度:如果您的文字较长,则您的internalContentSize的宽度会增加
  2. 换行符:如果添加\n则internalContentSize的宽度将是所有行的最大宽度。如果一行包含25个字符,另一行包含2个字符,另一行包含21个字符,则您的宽度将基于25个字符进行计算
  3. 允许的行数:您必须将设置为numberOfLines0否则您将没有多行。您numberOfLines将调整您的internalContentSize的高度
  4. 进行调整:想象一下,根据您的文本,您的internalContentSize的宽度为200and height为100,但是您想将宽度限制为标签容器的宽度,您将要做什么?解决方案是将其设置为所需的宽度。您可以通过设置preferredMaxLayoutWidth为来做到这一点,130然后新的internalContentSize的宽度大约为130。高度显然不止100是因为您需要更多的行。话虽这么说,如果您的约束设置正确,那么您根本就不需要使用它!有关更多信息,请参见此答案及其评论。您只需要使用“即可。但是如果您设置领先/追踪,则可以100%确定,那么您就很好了!preferredMaxLayoutWidth当您没有限制宽度/高度的约束时,因为您可能会说:“除非文字超出限制,否则不要包装文字”preferredMaxLayoutWidthnumberOfLines0长话短说,这里推荐使用它的大多数答案都是错误的!您不需要它。需要它是您约束的信号。设置不正确或您没有约束

  5. 字体大小:另外请注意,如果您增加fontSize,那么internalContentSize的高度将增加。我没有在代码中显示。您可以自己尝试。

回到您的tableViewCell示例:

您需要做的只是:

  • 设置numberOfLines0
  • 将标签正确地限制在边缘/边缘
  • 无需设置preferredMaxLayoutWidth

1

在我的情况下,我必须使用来自服务器的图像创建一个自定义单元,该图像可以是任何宽度和高度。和两个具有动态尺寸(宽度和高度)的UILabel

我已经在我的答案中通过自动布局和以编程方式实现了相同的目的:

基本上在@smileyBorg答案上面有帮助,但systemLayoutSizeFittingSize从来没有为我工作,在我的方法中:

1.不使用自动行高计算属性。2.不使用估计的高度。3.不需要不必要的更新约束。4.不使用自动首选最大版面宽度。5.不使用systemLayoutSizeFittingSize(应该使用但不为我工作,我不知道它在内部做什么),而是我的方法-(float)getViewHeight在工作,我知道它在内部做什么。

当我使用几种不同的方式显示单元格时,UITableView单元格中的高度可能会有所不同吗?


1

在我的情况下,填充的原因是sectionHeader和sectionFooter的高度,情节提要允许我将其更改为最小值1。因此,在viewDidLoad方法中:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

1

我只是做了一些愚蠢的尝试和错误使用的2个值rowHeightestimatedRowHeight,只是认为它可能提供一些见解调试:

如果同时设置它们或只设置,estimatedRowHeight则将获得所需的行为:

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

建议您尽最大的努力获得正确的估计,但最终结果没有不同。这只会影响您的表现。

在此处输入图片说明


如果仅设置rowHeight即只做:

tableView.rowHeight = UITableViewAutomaticDimension

您的最终结果将不理想:

在此处输入图片说明


如果将设置estimatedRowHeight为1或更小,则无论设置为,都会崩溃rowHeight

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

我崩溃并显示以下错误消息:

Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
    ...some other lines...

libc++abi.dylib: terminating with uncaught exception of type
NSException

1

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

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

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

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

其中w:是表格视图的宽度


0

只需在您的视图控制器中添加这两个功能,即可解决您的问题。在这里,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
}

-1
swift 4

    @IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var tableView: UITableView!
    private var context = 1
 override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
    }
  // Added observer to adjust tableview height based on the content

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &self.context{
            if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize{
                print("-----")
                print(size.height)
                tableViewHeightConstraint.constant = size.height + 50
            }
        }
    }

//Remove observer
 deinit {

        NotificationCenter.default.removeObserver(self)

    }

-1

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

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


-3

UITableView.automaticDimension 可以通过Interface Builder设置:

Xcode>故事板>大小检查器

表格视图单元>行高> 自动

尺寸检查器


-4

Swift中的另一个iOs7 + iOs8解决方案

var cell2height:CGFloat=44

override func viewDidLoad() {
    super.viewDidLoad()
    theTable.rowHeight = UITableViewAutomaticDimension
    theTable.estimatedRowHeight = 44.0;
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell =  tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
    cell2height=cell.contentView.height
    return cell
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if #available(iOS 8.0, *) {
        return UITableViewAutomaticDimension
    } else {
        return cell2height
    }
}

注意:在我的情况下
systemLayoutSizeFittingSize

中的单元格高度不正确cellForRowAtIndexPath,此时尚未设置单元格。
Andrii Chernenko

在iOs7中,它是固定值,即有效。您可以根据需要在cellForRowAtIndexPath外部设置
djdance '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.