1582

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

4

28 回答 28

2459

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

概念描述

无论您为哪个 iOS 版本开发,下面的前 2 个步骤都适用。

1. 设置和添加约束

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

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

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

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

正确设置约束绝对是使用 Auto Layout 获得动态单元高度的最困难和最重要的部分。如果你在这里犯了一个错误,它可能会阻止其他一切工作——所以慢慢来!我建议在代码中设置约束,因为您确切地知道哪些约束被添加到哪里,并且当出现问题时调试起来会容易得多。在代码中添加约束与使用布局锚点或 GitHub 上可用的出色开源 API 之一的 Interface Builder 一样简单且强大得多。

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

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

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

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

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

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

对于 iOS 8 - 自行调整大小的单元格

3.启用行高估计

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

Apple:使用自调整大小的表格视图单元格

在 iOS 8 中,Apple 已经内化了在 iOS 8 之前必须由您实现的大部分工作。为了允许自调整单元格机制工作,您必须首先将rowHeighttable view 上的属性设置为常量UITableView.automaticDimension. 然后,您只需通过将表视图的estimatedRowHeight属性设置为非零值来启用行高估计,例如:

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

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

一般来说,您提供的估算值不必非常准确 - 它仅用于正确调整表格视图中滚动指示器的大小,并且表格视图可以很好地调整滚动指示器以进行不正确的估算,因为您滚动屏幕上的单元格。您应该将estimatedRowHeight表视图(inviewDidLoad或类似)上的属性设置为一个常量值,即“平均”行高。仅当您的行高具有极大的可变性(例如相差一个数量级)并且您注意到滚动指示器在滚动时“跳跃”时,您才应该费心实施tableView:estimatedHeightForRowAtIndexPath:为每行返回更准确估计所需的最小计算。

对于 iOS 7 支持(自己实现自动单元格大小调整)

3. 进行布局传递并获取单元格高度

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

然后,强制单元格立即布局其子视图,然后使用'ssystemLayoutSizeFittingSize:上的方法找出单元格所需的高度是多少。用于获取适合单元格所有内容所需的最小尺寸。然后可以从委托方法返回高度。UITableViewCellcontentViewUILayoutFittingCompressedSizetableView:heightForRowAtIndexPath:

4.使用估计的行高

如果您的表格视图中有超过几十行,您会发现在第一次加载表格视图时执行自动布局约束求解会很快使主线程陷入困境,正如在第一次加载tableView:heightForRowAtIndexPath:时对每一行调用的那样(为了计算滚动指示器的大小)。

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

一般来说,您提供的估算值不必非常准确 - 它仅用于正确调整表格视图中滚动指示器的大小,并且表格视图可以很好地调整滚动指示器以进行不正确的估算,因为您滚动屏幕上的单元格。您应该将estimatedRowHeight表视图(inviewDidLoad或类似)上的属性设置为一个常量值,即“平均”行高。仅当您的行高具有极大的可变性(例如相差一个数量级)并且您注意到滚动指示器在滚动时“跳跃”时,您才应该费心实施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;
    }
}

示例项目

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

Xamarin (C#/.NET)

如果您使用的是 Xamarin,请查看@KentBoogaart 整理的这个示例项目

于 2013-09-11T16:48:39.377 回答
189

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

完整的示例代码在这里

于 2015-06-19T08:21:44.443 回答
99

可变高度 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
    

设置故事板

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

在此处输入图像描述

重要的提示:

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

其他 IB 设置

自定义类名和标识符

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

在此处输入图像描述

标签的零行

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

在此处输入图像描述

连接网点

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

完成的

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

笔记

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

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

也可以看看

于 2016-04-05T09:23:40.557 回答
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 中做这种爵士乐,但现在就在那里!

于 2014-06-11T15:18:21.517 回答
60

这是我的解决方案:

您需要在加载视图之前告诉TableViewestimatedHeight。否则它将无法像预期的那样表现。

Objective-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
}
于 2015-02-10T19:41:26.063 回答
47

@smileyborg 提出的解决方案几乎是完美的。如果您有一个自定义单元格并且您想要一个或多个UILabel具有动态高度的单元格,则systemLayoutSizeFittingSize方法与启用的 AutoLayout 相结合会返回 aCGSizeZero除非您将所有单元格约束从单元格移动到其 contentView (如 @TomSwift 在此处的建议How to resize superview to适合所有具有自动布局的子视图?)。

为此,您需要在自定义 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 答案与此混合应该可以。

于 2013-11-11T16:52:47.730 回答
25

我刚刚遇到了一个足够重要的问题,可以作为答案发布。

@smileyborg 的回答大部分是正确的。但是,如果您layoutSubviews的自定义单元类的方法中有任何代码,例如设置preferredMaxLayoutWidth,那么它将不会使用以下代码运行:

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

让我困惑了一阵子。然后我意识到这是因为那些只是触发 layoutSubviews contentView,而不是单元格本身。

我的工作代码如下所示:

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

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

于 2013-12-03T02:23:31.073 回答
20

(对于 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 所做的那样,如上所述。

于 2016-06-27T22:49:56.407 回答
19

以防人们仍然对此有问题。我写了一篇关于将 Autolayout 与 UITableViews 一起使用的快速博客文章 Leveraging Autolayout For Dynamic Cell Heights以及一个开源组件,以帮助使其更抽象和更易于实现。 https://github.com/Raizlabs/RZCellSizeManager

于 2014-03-05T15:53:36.960 回答
19

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

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

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

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

于 2014-04-29T18:16:45.197 回答
19

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

  • 分配和实现tableview dataSource和delegate
  • 分配UITableViewAutomaticDimension给 rowHeight 和estimatedRowHeight
  • 实现委托/数据源方法(即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(& 换行模式 = 截断尾部)
  • 设置关于其超级视图/单元格容器的所有约束(顶部、底部、左右)。
  • 可选:设置标签的最小高度,如果你想要标签覆盖的最小垂直区域,即使没有数据。

在此处输入图像描述

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

于 2017-10-24T11:31:04.127 回答
16

就像@Bob-Spryn一样,我遇到了一个足够重要的问题,我将其发布为答案。

我为@smileyborg 的回答苦苦挣扎了一段时间。我遇到的问题是,如果您在 IB 中定义了原型单元格并在 IB 中使用附加元素(UILabelsUIButtons等),当您使用 [ 实例化单元格时[YourTableViewCellClass alloc] init],它将不会实例化该单元格中的所有其他元素,除非您已经编写代码来做到这一点。(我也有类似的经历initWithStyle。)

要让情节提要实例化所有附加元素,请使用[tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"](不是[tableView dequeueReusableCellWithIdentifier:forIndexPath:]因为这会导致有趣的问题。)当您这样做时,您在 IB 中定义的所有元素都将被实例化。

于 2014-07-08T01:34:00.093 回答
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;
}
于 2014-08-22T10:14:19.613 回答
13

另一个“解决方案”:跳过所有这些挫折并使用 UIScrollView 来获得与 UITableView 外观和感觉相同的结果。

这对我来说是一个痛苦的“解决方案”,在我花了 20 多个非常令人沮丧的小时来尝试构建类似 Smileyborg 建议的东西并且在几个月和三个版本的 App Store 版本中失败之后。

我的看法是,如果你真的需要 iOS 7 支持(对我们来说,这是必不可少的),那么这项技术就太脆弱了,你会费尽心思去尝试。除非您使用一些高级行编辑功能和/或确实需要支持 1000 多个“行”(在我们的应用程序中,它实际上永远不会超过 20 行),否则 UITableView 通常完全是矫枉过正。

额外的好处是代码变得非常简单,而不是 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 解决问题之前阅读我的帖子。:)

于 2015-06-14T22:17:20.627 回答
13
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

在此处输入图像描述

于 2016-10-30T12:43:50.287 回答
11

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

然后我加了

[cell layoutSubviews];

在执行之前

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

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

于 2015-09-01T22:37:52.867 回答
9

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

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

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

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

就是这样。

于 2016-07-18T17:32:13.717 回答
8

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

三个规则/步骤

编号 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

于 2017-04-28T15:59:47.057 回答
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()留下来。观察行为。

然后取消注释step1,并观察。

然后取消注释 step2 并观察。

执行此操作,直到您取消注释所有 6 个步骤并观察它们的行为。

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

  1. 文本长度:如果您有更长的文本,那么您的 intrinsicContentSize 的宽度将会增加
  2. 换行符:如果添加\n,则 intrinsicContentSize 的宽度将是所有行的最大宽度。如果一行有 25 个字符,另一行有 2 个字符,另一行有 21 个字符,那么您的宽度将根据 25 个字符计算
  3. 允许的行数:您必须将 设置numberOfLines0否则您将不会有多行。您numberOfLines将调整您的内在内容大小的高度
  4. 进行调整:想象一下,根据您的文本,您的 intrinsicContentSize 的宽度为200,高度为100,但您想将宽度限制为标签的容器,您打算怎么做?解决方案是将其设置为所需的宽度。您可以通过设置preferredMaxLayoutWidth来做到这一点,130然后您的新 intrinsicContentSize 的宽度将大致为130. 高度显然会超过100因为你需要更多的线条。话虽如此,如果您的约束设置正确,那么您根本不需要使用它!有关更多信息,请参阅此答案及其评论。preferredMaxLayoutWidth仅当您没有限制宽度/高度的约束时才需要使用,因为有人可能会说“不要换行,除非它超过preferredMaxLayoutWidth“。但是如果你设置了前导/尾随,numberOfLines那么0你就很好了!长话短说,这里推荐使用它的大多数答案都是错误的!你不需要它。需要它表明你的约束设置不正确,或者您没有约束

  5. 字体大小:还要注意,如果你增加你的字体大小,那么内在内容大小的高度也会增加。我没有在我的代码中显示这一点。你可以自己试试。

回到您的 tableViewCell 示例:

您需要做的就是:

  • 设置numberOfLines0
  • 将标签正确地约束到边距/边缘
  • 无需设置preferredMaxLayoutWidth
于 2018-10-05T21:10:13.633 回答
1

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

我在我的回答中通过自动布局和编程实现了同样的效果:

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

1.不使用自动行高计算属性。2.不使用估计的高度 3.不需要不必要的updateConstraints。4.不使用自动首选最大布局宽度。5.没有使用systemLayoutSizeFittingSize(应该有用但不适合我,我不知道它在内部做什么),而是我的方法 -(float)getViewHeight 工作,我知道它在内部做什么。

当我使用几种不同的方式显示单元格时,UITableView 单元格中是否可以有不同的高度?

于 2016-05-27T06:12:15.393 回答
1

在我的情况下,填充是因为 sectionHeader 和 sectionFooter 高度,故事板允许我将其更改为最小 1。所以在 viewDidLoad 方法中:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
于 2017-01-25T07:35:59.090 回答
1

rowHeight我只是对and的 2 个值做了一些愚蠢的尝试和错误,estimatedRowHeight只是认为它可能会提供一些调试见解:

如果您同时设置它们或仅设置estimatedRowHeight您将获得所需的行为:

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

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

在此处输入图像描述


如果您只设置 rowHeight 即只做:

tableView.rowHeight = UITableViewAutomaticDimension

你的最终结果不会如你所愿:

在此处输入图像描述


如果将 设置为 1 或更小,estimatedRowHeight那么无论.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
于 2017-11-25T01:56:43.623 回答
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: 是 tableview 的宽度

于 2017-12-18T22:31:26.333 回答
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
}
于 2018-05-16T06:08:31.127 回答
-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)

    }
于 2019-03-01T06:40:05.223 回答
-1

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

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

于 2019-09-24T16:37:33.237 回答
-1

UITableView.automaticDimension可以通过界面生成器设置:

Xcode > 故事板 > 大小检查器

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

尺寸检查器

于 2019-12-31T22:03:02.427 回答
-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
    }
}
于 2016-01-16T15:13:49.643 回答