Iphone - 当每个单元格高度是动态时,何时计算 tableview 的 heightForRowAtIndexPath?
我已经看到这个问题被问了很多次,但令人惊讶的是,我没有看到一致的答案,所以我自己尝试一下:
如果您有一个包含您自己的自定义 UITableViewCells 的表视图,其中包含 UITextViews 和 UILabels ,其高度必须在运行时确定,您应该如何确定 heightForRowAtIndexPath 中每一行的高度?
最明显的第一个想法是通过计算 cellForRowAtIndexPath 内单元格内每个视图的高度,然后求和来计算每个单元格的高度,并存储最终的总高度以供以后检索。
但这不起作用,因为 cellForRowAtIndexPath 是在 heightForRowAtIndexPath 之后调用的。
我唯一能想到的就是在 viewDidLoad 中进行所有计算,然后创建所有 UITableViewCells,计算单元格高度并将其存储在 UITableViewCell 子类内的自定义字段中,并将每个单元格放入 NSMutableDictionary 中,并以 indexPath 作为键,然后使用 cellForRowAtIndexPath 和 heightForRowAtIndexPath 内的 indexPath 从字典中检索单元格,返回自定义高度值或单元格对象本身。
但这种方法似乎是错误的,因为它没有使用 dequeueReusableCellWithIdentifier,而是我会将所有单元格一次加载到控制器中的字典中,并且委托方法除了从字典中检索正确的单元格之外什么也不做。
但我没有看到任何其他方法可以做到这一点。这是一个坏主意吗?如果是这样,正确的方法是什么?
I have seen this question asked many times but astoundingly, I have not seen a consistent answer, so I will give it a try myself:
If you have a tableview containing your own custom UITableViewCells that contain UITextViews and UILabels whose height must be determined at runtime, how are you supposed to determine the height for each row in heightForRowAtIndexPath?
The most obvious first idea is to calculate the height for each cell by calculating and then summing the heights of each view inside the cell inside of cellForRowAtIndexPath, and store that final total height for later retrieval.
This will not work however because cellForRowAtIndexPath is called AFTER heightForRowAtIndexPath.
The only thing I can think of is to do all the calculations inside viewDidLoad, create all the UITableViewCells then, calculate the cells height and store that in a custom field inside your UITableViewCell subclass, and put each cell in an NSMutableDictionary with the indexPath as the the key, and then simply retrieve the cell from the dictionary using the indexPath inside cellForRowAtIndexPath and heightForRowAtIndexPath, returning either the custom height value or the cell object itself.
This approach seems wrong though because it does not make use of dequeueReusableCellWithIdentifier, instead I would be loading all the cells at once into a dictionary in my controller, and the delegate methods would be doing nothing more than retrieving the correct cell from the dictionary.
I don't see any other way to do it though. Is this a bad idea - if so, what is the correct way to do this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
Apple 实现 UITableView 的方式并不是每个人都直观,而且很容易误解
heightForRowAtIndexPath:
的作用。总体意图是,这是一种更快、占用内存更少的方法,可以非常频繁地为表中的每一行调用。这与cellForRowAtIndexPath:
形成鲜明对比,后者通常速度较慢且占用内存较多,但仅针对在任何给定时间实际需要显示的行进行调用。苹果为什么要这样实现呢? 部分原因是,计算行的高度几乎总是比构建和填充整个单元格更便宜(或者如果编码正确,可能会更便宜)。鉴于在许多表格中每个单元格的高度都是相同的,因此它通常要便宜得多。 另一部分原因是因为 iOS 需要知道整个表格的大小:这允许它创建滚动条并将其设置在滚动视图等上。
所以,除非每个单元格高度是相同的,那么当创建 UITableView 时,每当您向其发送 reloadData 消息时,数据源都会为每个单元格发送一个 heightForRowAtIndexPath 消息。因此,如果您的表格有 30 个单元格,则该消息将发送 30 次。假设这 30 个单元格中只有 6 个在屏幕上可见。在这种情况下,当创建时并向其发送 reloadData 消息时,UITableView 将为每个可见行发送一个 cellForRowAtIndexPath 消息,即该消息被发送六次。
有些人有时对如何在不创建视图本身的情况下计算单元格高度感到困惑。但通常这很容易做到。
例如,如果行高大小因包含不同数量的文本而变化,则可以对相关字符串使用
sizeWithFont:
方法之一来进行计算。这比构建视图然后测量结果更快。请注意,如果更改单元格的高度,则需要重新加载整个表格(使用 reloadData - 这将询问委托的每个高度,但仅询问可见单元格)或有选择地重新加载大小已改变的行更改(我上次检查时,还在每一行上调用heightForRowAtIndexPath:
但也做了一些滚动工作以达到良好的效果)。请参阅这个问题< /a> 也许还有这个。
The way Apple implements UITableView is not intuitive to everyone and it's easy to misunderstand the role of
heightForRowAtIndexPath:
. The general intention is that this is a faster and light-on-memory method that can be called for every row in the table quite frequently. This contrasts withcellForRowAtIndexPath:
which is often slower and more memory intensive, but is only called for the rows that are actually need to be displayed at any given time.Why do Apple implement it like this? Part of the reason is that it's almost always cheaper (or can be cheaper if you code it right) to calculate the height of a row than it is to build and populate a whole cell. Given that in many tables the height of every cell will be identical, it is often vastly cheaper. And another part of the reason is because iOS needs to know the size of the whole table: this allows it to create the scroll bars and set it up on a scroll view etc.
So, unless every cell height is the same, then when a UITableView is created and whenever you send it a reloadData message, the datasource is sent one heightForRowAtIndexPath message for each cell. So if your table has 30 cells, that message gets sent 30 times. Say only six of those 30 cells are visible on screen. In that case, when created and when you send it a reloadData message, the UITableView will send one cellForRowAtIndexPath message per visible row, i.e. that message gets sent six times.
Some people are sometimes puzzled about how to calculate a cell height without creating the views themselves. But usually this is easy to do.
For example, if your row heights vary in size because they hold varying amounts of text, you can use one of the
sizeWithFont:
methods on the relevant string to do the calculations. This is quicker than building a view and then measuring the result. Note, that if you change the height of a cell, you will need to either reload the whole table (with reloadData - this will ask the delegate for every height, but only ask for visible cells) OR selectively reload the rows where the size has changed (which, last time I checked, also callsheightForRowAtIndexPath:
on ever row but also does some scrolling work for good measure).See this question and perhaps also this one.
因此,我认为您可以做到这一点,而不必一次创建所有单元(正如您所建议的,这很浪费,而且对于大量单元来说可能不切实际)。
UIKit 向 NSString 添加了一些方法,您可能错过了它们,因为它们不是主要 NSString 文档的一部分。从您感兴趣的开始:
这是 Apple 文档。
理论上,这些 NSString 添加是为了解决这个问题:计算出文本块将占用的大小,而无需加载视图本身。您可能已经可以访问作为表视图数据源一部分的每个单元格的文本。
我说“理论上”是因为如果您在 UITextView 中进行格式化,您的效果可能会因该解决方案而异。但我希望它至少能帮助你实现这一目标。 Cocoa is My Girlfriend 上有一个这样的示例。
So, I think you can do this without having to create your cells all at once (which, as you suggest, is wasteful and also probably impractical for a large number of cells).
UIKit adds a couple of methods to NSString, you may have missed them as they're not part of the main NSString documentation. The ones of interest to you begin:
Here is the link to the Apple docs.
In theory, these NSString additions exist for this exact problem: to figure out the size that a block of text will take up without needing to load the view itself. You presumably already have access to the text for each cell as part of your table view datasource.
I say 'in theory' because if you're doing formatting in your UITextView your mileage may vary with this solution. But I'm hoping it will get you at least part way there. There's an example of this on Cocoa is My Girlfriend.
我过去使用的一种方法是创建一个类变量来保存将在表中使用的单元格的单个实例(我将其称为原型单元格)。然后在自定义单元格类中,我有一个方法来填充数据并确定单元格所需的高度。请注意,它可以是真正填充数据的方法的更简单变体 - 例如,它可以只使用 NSString height 方法来确定 UILabel 在最终单元格中的高度,而不是实际调整单元格中 UILabel 的大小,并且然后使用单元格总高度(加上底部的边框)和 UILabel 位置来确定实际高度。您使用原型单元只是为了了解元素的放置位置,以便您知道当标签高度为 44 个单位时这意味着什么。
然后,在
heightForRow:
中,我调用该方法来返回高度。在 cellForRow: 中,我使用实际填充标签并调整它们大小的方法(您永远不会自己调整 UITableView 单元格的大小)。
如果你想变得更奇特,你还可以根据你传入的数据缓存每个单元格的高度(例如,如果这就是决定高度的全部,它可以只在一个 NSString 上)。如果您有大量通常相同的数据,那么拥有永久缓存而不仅仅是内存中可能更有意义。
您还可以尝试根据字符或字数估计行数,但根据我的经验,这永远不会起作用 - 当它出错时,通常会弄乱一个单元格及其下面的所有单元格。
An approach I have used in the past is to create a class variable to hold a single instance of the cell you are going to be using in the table (I call it a prototype cell). Then in the custom cell class I have a method to populate the data and determine the height the cell needs to be. Note that it can be a simpler variant of the method to really populate the data - instead of actually resizing a UILabel in a cell for example, it can just use the NSString height methods to determine how tall the UILabel would be in the final cell and then use the total cell height (plus a border on the bottom) and UILabel placement to determine the real height. YOu use the prototype cell just to get an idea of where elements are placed so you know what it means when a label is going to be 44 units high.
In
heightForRow:
I then call that method to return the height.In
cellForRow:
I use the method that actually populates labels and resizes them (you never resize the UITableView cell yourself).If you want to get fancy, you can also cache the height for each cell based on the data you pass in (for instance it could just be on one NSString if that's all that determines height). If you have a lot of data that's often the same it may make sense to have a permanent cache instead of just in-memory.
You can also try estimating line count based on character or word count, but in my experience that never works - and when it goes wrong it usually messes up a cell and all the cells below it.
这就是我根据 UTextView 中的文本量计算单元格高度的方法:
当然,您需要调整
PADDING
和其他变量以适合您的需要,但这会根据提供的文本量设置其中包含 UITextView 的单元格的高度。因此,如果只有 3 行文本,则单元格相当短,而如果有 14 行文本,则单元格高度相当大。This is how I calculate the height of a cell based on the amount of text in a UTextView:
Of course, you would need to adjust the
PADDING
and other variables to fit your needs, but this sets the height of the cell which has aUITextView
in it, based on the amount of text supplied. so if there are only 3 lines of text, the cell is fairly short, where as if there are 14 lines of text, the cell is rather large in height.我见过的最好的实现方式是 Three20 TTTableView 类的实现方式。
基本上,他们有一个派生自 UITableViewController 的类,该类将 heightForRowAtIndexPath: 方法委托给 TTTableCell 类上的类方法。
然后,该类总是通过执行与绘制方法中相同的布局计算来返回正确的高度。通过将其移动到类中,可以避免编写依赖于单元实例的代码。
实际上没有其他选择 - 出于性能原因,框架不会在询问单元格的高度之前创建单元格,并且如果可能有很多行,您也不想这样做。
The best implementation of this that I've seen is the way the Three20 TTTableView classes do it.
Basically they have a class derived from UITableViewController that delegates the
heightForRowAtIndexPath:
method to a class method on a TTTableCell class.That class then returns the right height, invariably by doing the same sort of layout calculations as you do in the draw methods. By moving it to the class it avoids writing code that depends on the cell instance.
There's really no other option - for performance reasons the framework won't create cells before asking for their heights, and you don't really want to do that either if there could be a lot of rows.
将每个单元格的计算移至 tableView:heightForRowAtIndexPath: 的问题是,每次调用 reloadData 时都会重新计算所有单元格。太慢了,至少对于我的应用程序来说,可能有 100 行。这是一个替代解决方案,它使用默认行高,并在计算行高时缓存行高。当高度发生变化或首次计算时,会安排表重新加载以通知表视图新的高度。这确实意味着当行的高度发生变化时,行会显示两次,但相比之下这是次要的:
tableView:heightForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
The problem with moving the calculation of each cell to tableView:heightForRowAtIndexPath: is that all the cells are then recalculated every time reloadData is called. Way too slow, at least for my application where there may be 100's of rows. Here's an alternative solution that uses a default row height, and caches the row heights when they are calculated. When a height changes, or is first calculated, a table reload is scheduled to inform the table view of the new heights. This does mean that rows are displayed twice when their heights change, but that's minor in comparison:
tableView:heightForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
非常好的问题:也在寻找对此的更多见解。
澄清问题:
一些解决方案出奇地简单/有效:
解决方案 1:强制 heightForRowAtIndexPath 计算单元格的规格。 Massimo Cafaro 9 月 9 日
解决方案 2:对单元格执行第一遍“标准尺寸”,当您确实有单元格高度时缓存结果,然后使用新高度重新加载表格 - 对称
解决方案 3:另一个有趣的答案似乎是涉及 Three20 但基于答案似乎没有在 Storyboard/xib 中绘制一个单元格,这将使这个“问题”更容易解决。
Really good question: looking for more insight on this as well.
Clarifying the issue:
Some of the solutions are surprisingly simple/effective:
solution 1: force the heightForRowAtIndexPath to calculate the the cell's specs. Massimo Cafaro Sept 9th
solution 2: do a first pass "standard size" for the cells, cache results when you do have cell heights, then reload the table using the new heights - Symmetric
solution 3: the other interesting answer seems to be the involving three20 but based on the answer it seems that there isn't a cell drawn in storyboard/xib which would make this "problem" much easier to solve.
我采用了我最初提出的想法,该想法似乎工作得很好,我在 viewDidLoad 中提前加载所有自定义单元格,将它们存储在 NSMutableDictionary 中,并以其索引作为键。我正在发布相关代码,并且希望任何人对这种方法有任何批评或意见。具体来说,我不确定我在 viewDidLoad 中从笔尖创建 UITableViewCells 的方式是否存在内存泄漏问题 - 因为我不释放它们。
I went with the idea I originally proposed, which appears to work fine, whereby I load all the custom cells ahead of time in viewDidLoad, store them in a NSMutableDictionary with their index as the key. I am posting the relevant code and would love any critiques or opinions anyone has about this approach. Specifically, I am not sure whether there is any memory leak issue with the way I am creating the UITableViewCells from the nib in viewDidLoad - since I don't release them.
这是我在非常简单的情况下所做的事情,一个单元格包含标签中的注释。注释本身受到我施加的最大长度的限制,因此我使用多行 UILabel 并动态计算每个单元格的正确八个,如以下示例所示。您可以以几乎相同的方式处理 UITextView。
Here is what I do in very simple case, a cell containing a note held in a label. The note itself is constrained to a maximum length I am imposing, so I use a multi-line UILabel and I compute dynamically the correct eight for each cell as shown in the following example. You can deal with an UITextView pretty much the same.
当我一遍又一遍地搜索这个话题时,我终于想到了这个逻辑。一个简单的代码,但可能不够高效,但到目前为止这是我能找到的最好的代码。
这是其余的代码
as i searched over and over about this topic, finally this logic came to my thought. a simple code, but maybe not efficient enough, but so far it's the best i can find.
and here is the rest of the code