在UITableView的使用自动布局动态单元布局和放大器;可变行高度 [英] Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

查看:425
本文介绍了在UITableView的使用自动布局动态单元布局和放大器;可变行高度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

你怎么在表视图中使用自动布局中的的UITableViewCell s到让每一个单元格的内容和子视图确定该行的高度,同时保持平滑滚动性能?

How do you use Auto Layout within UITableViewCells in a table view to let each cell's content and subviews determine the row height, while maintaining smooth scrolling performance?

推荐答案

TL; DR:不喜欢读书?直接跳到示例项目在GitHub上:

TL;DR: Don't like reading? Jump straight to the sample projects on GitHub:

  • iOS 8 Sample Project - Requires iOS 8
  • iOS 7 Sample Project - Works on iOS 7+

下面的第2步骤适用不管是哪个版本的iOS你正在开发。的

The first 2 steps below are applicable regardless of which iOS versions you are developing for.

在你的的UITableViewCell 子类,添加约束,使细胞的子视图有其边缘固定在边缘的细胞的内容查看(最重要的是在顶部和底部边缘)。 注意:不要钉子视图电池本身;只有细胞的内容查看让这些子视图的内在含量的大小通过确保驱动表视图单元格的内容视图的高度的内容COM pression电阻的和的内容拥抱的为每个子视图的垂直尺寸的限制不是由您添加更高优先级的约束覆盖。 (<一href=\"http://stackoverflow.com/questions/22599069/what-is-the-content-com$p$pssion-resistance-and-content-hugging-of-a-uiview\">Huh?请点击此处。)

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). NOTE: don't pin subviews to the cell itself; only to the cell's contentView! Let the intrinsic content size of these subviews drive the height of the table view cell's content view by making sure the content compression 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.)

记住,这个想法是有垂直连接到电池的内容视图细胞的子视图,使他们能够发挥pressure,并在内容视图中展开,以适应他们。用一个例子细胞与几个子视图,这里是一个直观的说明了什么部分 (不是全部!)你的约束的需要如下:

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

让您的制约权肯定是在最难和最重要的组成部分越来越具有自动布局工作动态小区高度即可。如果你犯了错误,它可以prevent一切从其他的工作 - 所以把你的时间!我建议在code设置你的约束,因为你确切地知道哪些被添加约束的地方,它是一个更容易调试出问题时。在code添加约束也一样,当你利用现有的梦幻般的开源API之一一样方便,比Interface Builder的显著更强大 - 在这里就是我的设计,维护和独占使用:的https://github.com/smileyborg/PureLayout

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 is just as easy as and significantly more powerful than Interface Builder when you leverage one of the fantastic open source APIs available -- here is the one I design, maintain, and use exclusively: https://github.com/smileyborg/PureLayout


  • 如果您是在code添加约束,你应该这样做,从你的UITableViewCell子类的 updateConstraints 方法中的一次。需要注意的是 updateConstraints 可称为不止一次,所以要避免添加相同的约束不止一次,请务必在<$ C包住约束增加code $ C> updateConstraints 在布尔属性的检查,如 didSetupConstraints (你你运行你的约束增加$ C $后设置为YES C钮一次)。在另一方面,如果你有code的更新现有的约束(如调整上的一些约束属性),将本在 updateConstraints 但支票 didSetupConstraints 之外,因此它可以运行每次方法被调用。

  • If you're adding constraints in code, you should do this once from within the updateConstraints method of your UITableViewCell subclass. 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). 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.

对于每一个独特的一套在细胞内的限制,使用独特的细胞再利用标识。换句话说,如果你的细胞有多个独特的布局,每一个独特的布局应该接受自己的重用标识符。 (一个很好的提示,你需要使用一个新的重用标识符是当你的细胞变异有不同数量的子视图或子视图被安排在一个鲜明的时尚。)

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

例如,如果你在每个单元格显示一个电子邮件消息,则可能有4个独特的布局:只有一个主题的消息,消息与对象和主体,信息以主题和照片附件,和消息一个主题,正文和照片附件。每个布局具有实现它所需完全不同的限制,所以,一旦细胞被初始化和约束添加对这些细胞类型中的一个,所述细胞应该得到特定于该细胞类型的唯一的重用标识符。这意味着,当你出队重用,约束已经添加单元格,并准备去该细胞类型。

For example, if you were displaying an email message in each cell, you might have 4 unique layouts: messages with just a subject, messages with a subject and a body, messages with a subject and a photo attachment, and messages with a subject, body, and photo attachment. Each layout has completely different constraints required to achieve it, so once the cell is initialized and the constraints are added for one of these cell types, the cell should get a unique reuse identifier specific to that cell type. This means when you dequeue a cell for reuse, the constraints have already been added and are ready to go for that cell type.

请注意,由于在固有内容大小的差异,具有相同的约束(类型)细胞可能仍然具有不同的高度!不要混淆根本不同的布局(不同的约束),用不同的计算视图帧(来自相同的约束解决)由于不同规模的内容。

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.


  • 请不要用完全不同的约束集添加细胞相同的重用池(即使用相同的重用标识符),然后尝试每次出队后,从头开始删除旧的约束和设置新的限制。内部自动布局引擎不是设计来处理约束大规模的变化,你会看到巨大的性能问题。

使用的iOS 8,苹果已经内化多,$ P $工作pviously必须由你事先到iOS 8.实施为了让自上浆细胞的工作机制,必须首先设置的rowHeight 在桌子上欣赏到恒 UITableViewAutomaticDimension 属性。然后,你只需要通过表视图的 estimatedRowHeight 属性设置为非零值,例如使行的高度评价:

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 UITableViewAutomaticDimension. Then, you simply need to enable row height estimation by setting the table view's estimatedRowHeight property to a nonzero value, for example:

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

这样做是提供对于那些尚未屏幕细胞的行高的临时估计/占位符的表视图。然后,当这些细胞将要在屏幕上滚动,实际行高度将被计算。要确定每一行的实际高度,表视图自动要求其需要进行基于内容视图的已知的固定宽度(这是基于什么高度内容查看每个单元上表视图的宽度,减去像一个部分索引或附件查看任何额外的东西),自动布局约束已添加到单元格的内容视图和子视图。一旦实际单元格高度已经确定,该行的旧估计高度与新的实际高度(并表视图的contentSize / contentOffset任何必要的调整对您制成)更新。

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

一般而言,您提供的估计不一定很准确 - 它只是用来正确尺寸表视图滚动指标,表视图不会调整滚动指示不正确的一个好工作估计当滚动屏幕上的细胞。你应该设置在表视图 estimatedRowHeight 属性(在 viewDidLoad中或类似),以一个恒定的值是平均的行高。的只有当你的行高有极端的变化(例如,通过一个数量级不同),并且注意到了滚动指示跳楼为您滚动你应该费心实施的tableView:estimatedHeightForRowAtIndexPath:来执行以返回一个更准确的估计为每一行所需的最小计算

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. Only if your row heights have extreme variability (e.g. differ by an order of magnitude) and you notice the scroll indicator "jumping" as you scroll should you bother implementing tableView:estimatedHeightForRowAtIndexPath: to do the minimal calculation required to return a more accurate estimate for each row.

首先,实例化一个表格视图单元格的屏幕外实例的每个重用标识符一个实例的,就是严格用于高度计算。 (画外意的单元格引用存储在一个属性/伊娃视图控制器上,从来没有从返回的tableView:的cellForRowAtIndexPath:为表视图,以实际呈现在屏幕上)接下来,细胞必须与确切内容(例如文本,图像等),这将持有,如果它是要显示在表视图被构造

First, instantiate an offscreen instance of a table view cell, one instance for each reuse identifier, that is used strictly for height calculations. (Offscreen meaning the cell reference is stored in a property/ivar on the view controller and never returned from tableView:cellForRowAtIndexPath: for the table view to actually render onscreen.) Next, the cell must be configured with the exact content (e.g. text, images, etc) that it would hold if it were to be displayed in the table view.

然后,迫使立即布局其子视图的单元格,然后使用 systemLayoutSizeFittingSize:的方法的UITableViewCell 内容查看来找出细胞所需的高度是什么。使用 UILayoutFittingCom pressedSize 去适应单元格中的所有内容所需的最小尺寸。高度然后可以从的tableView返回:heightForRowAtIndexPath:委托方法

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. Use UILayoutFittingCompressedSize to get the smallest size required to fit all the contents of the cell. The height can then be returned from the tableView:heightForRowAtIndexPath: delegate method.

如果您的表视图比在它一对夫妇十几行多了,你会发现,做自动布局约束求解可以迅速陷入瘫痪时,首先加载表视图,为的tableView的主线: heightForRowAtIndexPath:被称为对每行根据第一负载(为了计算滚动指示的大小)

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

由于iOS的7,你可以(而且应该绝对)的使用上表视图 estimatedRowHeight 属性。这样做是提供对于那些尚未屏幕细胞的行高的临时估计/占位符的表视图。然后,当这些细胞将要在屏幕上滚动,实际行高度将被计算(通过调用的tableView:heightForRowAtIndexPath ),以及所估计的高度与实际一个更新的。

As of iOS 7, you can (and absolutely should) use the estimatedRowHeight property on the table view. What this does is provide the table view with a temporary estimate/placeholder for the row heights of cells that are not yet onscreen. Then, when these cells are about to scroll onscreen, the actual row height will be calculated (by calling tableView:heightForRowAtIndexPath:), and the estimated height updated with the actual one.

一般而言,您提供的估计不一定很准确 - 它只是用来正确尺寸表视图滚动指标,表视图不会调整滚动指示不正确的一个好工作估计当滚动屏幕上的细胞。你应该设置在表视图 estimatedRowHeight 属性(在 viewDidLoad中或类似),以一个恒定的值是平均的行高。的只有当你的行高有极端的变化(例如,通过一个数量级不同),并且注意到了滚动指示跳楼为您滚动你应该费心实施的tableView:estimatedHeightForRowAtIndexPath:来执行以返回一个更准确的估计为每一行所需的最小计算

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. Only if your row heights have extreme variability (e.g. differ by an order of magnitude) and you notice the scroll indicator "jumping" as you scroll should you bother implementing tableView:estimatedHeightForRowAtIndexPath: to do the minimal calculation required to return a more accurate estimate for each row.

如果你做了上述所有的和做的限制在解决的tableView时仍在寻找性能是太慢:heightForRowAtIndexPath:,你会遗憾的是需要实现一些缓存单元格高度。 (这是由苹果的工程师提出的办法)。总的想法就是让自动布局引擎解决制约的第一次,然后缓存该小区计算出的高度和使用缓存值,该单元的身高今后所有申请。当然,关键是要确保您清除缓存的高度为一个单元时,有什么事情发生,可能导致细胞的高度变化 - 主要是,这将在其他重要事件发生时(如用户调整是在该单元格的内容发生变化或动态类型的文字大小滑块)。

If you've done all the above and are still finding that performance is unacceptably slow when doing the constraint solving in tableView:heightForRowAtIndexPath:, you'll unfortunately need to implement some caching for cell heights. (This is the approach suggested by Apple's engineers.) The general idea is to let the Auto Layout engine solve the constraints the first time, then cache the calculated height for that cell and use the cached value for all future requests for that cell's height. The trick of course is to make sure you clear the cached height for a cell when anything happens that could cause the cell's height to change -- primarily, this would be when that cell's content changes or when other important events occur (like the user adjusting the Dynamic Type text size slider).

- (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.0f, 0.0f, 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 multi-line 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.0f;

    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.0f;
    } else {
        return 40.0f;
    }
}

示例项目


  • 的iOS 8示例项目 - 需要iOS 8

  • 的iOS 7示例项目 - 适用于iOS 7 +

  • Sample Projects

    • iOS 8 Sample Project - Requires iOS 8
    • iOS 7 Sample Project - Works on iOS 7+
    • 这些项目是由于含有UILabels动态内容表视图细胞与可变行高度表视图完全工作的例子。

      These projects are fully working examples of table views with variable row heights due to table view cells containing dynamic content in UILabels.

      随意提高你运行任何疑问或问题纳入(你可以在GitHub上公开的问题,或在这里发表评论)。我会尽我所能帮助!

      Feel free to raise any questions or issues you run into (you can open issues on GitHub or post comments here). I'll try my best to help!

      如果你使用Xamarin,看看这个示例项目通过的放在一起://计算器.COM /用户/ 5380 /肯特boogaart> @ KentBoogaart

      If you're using Xamarin, check out this sample project put together by @KentBoogaart.

      这篇关于在UITableView的使用自动布局动态单元布局和放大器;可变行高度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆