注册
登录
IOS
在UITableView中使用自动布局以获取动态单元格布局和可变的行高
返回
在UITableView中使用自动布局以获取动态单元格布局和可变的行高
作者:
狗头军师
发布时间:
2024-12-18 08:33:40 (3天前)
如何UITableViewCell在表格视图的s中使用“自动布局”,以使每个单元格的内容和子视图(自身/自动)确定行高,同时保持流畅的滚动性能?
收藏
举报
2 条回复
1#
回复此人
v-star*위위
|
2020-08-12 10-32
TL; DR:不喜欢阅读?直接跳转到GitHub上的示例项目: iOS 8示例项目 -需要iOS 8 iOS 7示例项目 -适用于iOS 7+ 概念描述 无论您针对哪个iOS版本开发,以下前两个步骤均适用。 1.设置和添加约束 在UITableViewCell子类中,添加约束,以使单元格的子视图的边缘固定到单元格的contentView的边缘(最重要的是固定在顶部和底部边缘)。注意:不要将子视图固定到单元格本身;只对细胞的contentView!让这些子视图的固有内容大小驱动表格视图单元格的内容视图的高度,方法是确保您添加的更高优先级约束不会覆盖每个子视图的垂直方向上的内容压缩阻力和内容包含约束。(嗯?点击这里。) 请记住,其想法是使单元的子视图垂直连接到单元的内容视图,以便它们可以“施加压力”并使内容视图扩展以适合它们。使用带有几个子视图的示例单元格,这是一些 (不是全部!)约束看起来像的视觉插图: 对表视图单元格的约束的示例说明。 您可以想象,随着在上面的示例单元格中将更多文本添加到多行主体标签中,它将需要垂直增长以适合文本,这将有效地迫使单元格高度增加。(当然,您需要正确设置约束条件才能使其正常工作!) 正确设置约束绝对是使用自动版式获得动态像元高度时最困难也是最重要的部分。如果您在此处输入错误,则可能会阻止其他所有功能运行-因此,请花点时间!我建议在代码中设置约束,因为您确切知道哪些约束要添加到哪里,并且在出现问题时调试起来要容易得多。与使用布局锚点或GitHub上提供的一种出色的开源API之一的Interface Builder相比,在代码中添加约束既简单又功能强大。 如果要在代码中添加约束,则应在updateConstraintsUITableViewCell子类的方法中执行一次。请注意,它updateConstraints可能会被多次调用,因此,为避免多次添加相同的约束,请确保将添加约束的代码包装updateConstraints在布尔值检查中,例如didSetupConstraints(在运行约束后将其设置为YES) -一次添加代码)。另一方面,如果您有更新现有约束的代码(例如constant在某些约束上调整属性),请将其放在updateConstraints检查对象的外部,didSetupConstraints以便每次调用该方法时都可以运行。 2.确定唯一的表视图单元重用标识符 对于单元中的每个唯一约束集,请使用唯一的单元重用标识符。换句话说,如果您的单元具有多个唯一的布局,则每个唯一的布局应收到其自己的重用标识符。(当单元变体具有不同数量的子视图,或者子视图以不同的方式排列时,就需要使用新的重用标识符。) 例如,如果您在每个单元格中显示一封电子邮件,则可能有4种独特的布局:仅包含主题的邮件,包含主题和正文的邮件,包含主题和照片附件的邮件以及包含主题的邮件,机身和照片附件。每个布局都有实现它所需的完全不同的约束,因此,一旦初始化了单元并为这些单元类型之一添加了约束,该单元就应获得特定于该单元类型的唯一重用标识符。这意味着当您将单元出队以供重用时,约束已被添加并且可以用于该单元类型。 请注意,由于内部内容大小的不同,具有相同约束(类型)的单元格可能仍具有不同的高度!不要将根本不同的布局(不同的约束)与由于内容大小不同而计算出的不同视图框架(由相同约束解决)混淆。 不要在完全相同的重用池中添加具有完全不同的约束集的单元(即,使用相同的重用标识符),然后在每次出队后尝试删除旧约束并从头开始设置新约束。内部的自动版图引擎并非旨在处理约束中的大规模更改,您将看到大量的性能问题。 对于iOS 8-自定义单元格 3.启用行高估算 要启用自动调整大小的表格视图单元格,必须将表格视图的rowHeight属性设置为UITableViewAutomaticDimension。您还必须将一个值分配给estimateRowHeight属性。一旦设置了这两个属性,系统就会使用“自动布局”来计算行的实际高度 苹果:使用自动调整大小的表格视图单元 在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:该方法UITableViewCell的contentView找出电池的必要高度是什么。使用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; } } ``` 样例项目 iOS 8示例项目 -需要iOS 8 iOS 7示例项目 -适用于iOS 7+ 这些项目是具有可变行高的表格视图的完全可用示例,这是由于表格视图单元格包含UILabels中的动态内容。 Xamarin(C#/。NET)
编辑
登录
后才能参与评论