ios 如何检测 UITableView 的加载结束
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4163579/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to detect the end of loading of UITableView
提问by emenegro
I want to change the offset of the table when the load is finished and that offset depends on the number of cells loaded on the table.
我想在加载完成后更改表格的偏移量,该偏移量取决于加载到表格中的单元格数量。
Is it anyway on the SDK to know when a uitableview loading has finished? I see nothing neither on delegate nor on data source protocols.
无论如何,SDK 是否知道 uitableview 加载何时完成?我在委托和数据源协议上都看不到任何内容。
I can't use the count of the data sources because of the loading of the visible cells only.
由于仅加载了可见单元格,我无法使用数据源的计数。
回答by folex
Improve to @RichX answer:
lastRow
can be both [tableView numberOfRowsInSection: 0] - 1
or ((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row
.
So the code will be:
改进为@RichX 答案:
lastRow
可以是两者[tableView numberOfRowsInSection: 0] - 1
或((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row
。所以代码将是:
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath row] == ((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row){
//end of loading
//for example [activityIndicator stopAnimating];
}
}
UPDATE:Well, @htafoya's comment is right. If you want this code to detect end of loading all data from source, it wouldn't, but that's not the original question. This code is for detecting when all cells that are meant to be visible are displayed. willDisplayCell:
used here for smoother UI (single cell usually displays fast after willDisplay:
call). You could also try it with tableView:didEndDisplayingCell:
.
更新:嗯,@ htafoya的评论是正确的。如果您希望此代码检测从源加载所有数据的结束,则不会,但这不是最初的问题。该代码可用于显示的意图是明显的所有单元时检测。willDisplayCell:
这里使用更流畅的用户界面(单细胞通常显示后快速willDisplay:
调用)。您也可以尝试使用tableView:didEndDisplayingCell:
.
回答by Daniele Ceglia
Swift 3 & 4 & 5 version:
斯威夫特 3 & 4 & 5 版本:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let lastVisibleIndexPath = tableView.indexPathsForVisibleRows?.last {
if indexPath == lastVisibleIndexPath {
// do here...
}
}
}
回答by RichX
I always use this very simple solution:
我总是使用这个非常简单的解决方案:
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath row] == lastRow){
//end of loading
//for example [activityIndicator stopAnimating];
}
}
回答by Kyle Clegg
Here's another option that seems to work for me. In the viewForFooter delegate method check if it's the final section and add your code there. This approach came to mind after realizing that willDisplayCell doesn't account for footers if you have them.
这是另一个似乎对我有用的选项。在 viewForFooter 委托方法中检查它是否是最后一部分并在那里添加您的代码。在意识到 willDisplayCell 不考虑页脚(如果您有页脚)之后,我想到了这种方法。
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
// Perform some final layout updates
if (section == ([tableView numberOfSections] - 1)) {
[self tableViewWillFinishLoading:tableView];
}
// Return nil, or whatever view you were going to return for the footer
return nil;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
// Return 0, or the height for your footer view
return 0.0;
}
- (void)tableViewWillFinishLoading:(UITableView *)tableView
{
NSLog(@"finished loading");
}
I find this approach works best if you are looking to find the end loading for the entire UITableView
, and not simply the visible cells. Depending on your needs you may only want the visible cells, in which case folex's answeris a good route.
如果您希望找到整个 的最终加载UITableView
,而不仅仅是可见单元格,我发现这种方法最有效。根据您的需要,您可能只想要可见的单元格,在这种情况下,folex 的答案是一个很好的途径。
回答by fatihyildizhan
Swift 2 solution:
斯威夫特 2 解决方案:
// willDisplay function
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let lastRowIndex = tableView.numberOfRowsInSection(0)
if indexPath.row == lastRowIndex - 1 {
fetchNewDataFromServer()
}
}
// data fetcher function
func fetchNewDataFromServer() {
if(!loading && !allDataFetched) {
// call beginUpdates before multiple rows insert operation
tableView.beginUpdates()
// for loop
// insertRowsAtIndexPaths
tableView.endUpdates()
}
}
回答by malhal
Try this magic:
试试这个魔法:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// cancel the perform request if there is another section
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView];
// create a perform request to call the didLoadRows method on the next event loop.
[self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0];
return self.objects.count;
}
// called after the rows in the last section is loaded
-(void)tableViewDidLoadRows:(UITableView*)tableView{
// make the cell selected after all rows loaded
if(self.selectedObject){
NSInteger index = [self.objects indexOfObject:self.selectedObject];
[tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] animated:NO scrollPosition:UITableViewScrollPositionMiddle];
}
}
The behaviour of table loading means you can't call select row until the table knows the row counts and I wanted a row selected by default. I had a table view delegate that wasn't a view controller so I couldn't simply put the table cell select in the view appeared or loaded delegate methods, and none of the other answers were to my liking.
表加载的行为意味着你不能调用 select row 直到表知道行数并且我想要默认选择一行。我有一个不是视图控制器的表格视图委托,所以我不能简单地将表格单元格选择放在出现的视图或加载的委托方法中,并且其他答案都不符合我的喜好。
回答by Travis M.
For the chosen answer version in Swift 3:
对于 Swift 3 中选择的答案版本:
var isLoadingTableView = true
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if tableData.count > 0 && isLoadingTableView {
if let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows, let lastIndexPath = indexPathsForVisibleRows.last, lastIndexPath.row == indexPath.row {
isLoadingTableView = false
//do something after table is done loading
}
}
}
I needed the isLoadingTableView variable because I wanted to make sure the table is done loading before I make a default cell selection. If you don't include this then every time you scroll the table it will invoke your code again.
我需要 isLoadingTableView 变量,因为我想确保在我选择默认单元格之前完成表格加载。如果您不包含此内容,则每次滚动表格时,它都会再次调用您的代码。
回答by René Retz
The best approach that I know is Eric's answer at: Get notified when UITableView has finished asking for data?
我所知道的最佳方法是 Eric 的回答:在 UITableView 完成请求数据时收到通知?
Update: To make it work I have to put these calls in -tableView:cellForRowAtIndexPath:
更新:为了让它工作,我必须把这些电话放进去 -tableView:cellForRowAtIndexPath:
[tableView beginUpdates];
[tableView endUpdates];
回答by GaétanZ
To know when a table view finishes loading its content, we first need to have a basic understanding of how the views are put on screen.
要知道 table view 何时完成加载其内容,我们首先需要对视图如何放置在屏幕上有一个基本的了解。
In the life cycle of an app, there are 4 key moments :
在应用程序的生命周期中,有 4 个关键时刻:
- The app receives an event (touch, timer, block dispatched etc)
- The app handles the event (it modifies a constraint, starts an animation, changes background etc)
- The app computes the new view hierarchy
- The app render the view hierarchy and display it
- 应用程序接收事件(触摸、计时器、分派块等)
- 应用程序处理事件(它修改约束、启动动画、更改背景等)
- 应用程序计算新的视图层次结构
- 应用程序呈现视图层次结构并显示它
The 2 and 3 times are totally separated. Why ?For performance reasons, we don't want to perform all the computations of the moment 3 each time a modification is done.
2次和3次是完全分开的。为什么 ?出于性能原因,我们不想在每次修改完成时都执行第 3 时刻的所有计算。
So, I think you are facing a case like this :
所以,我认为你正面临这样的情况:
tableView.reloadData()
tableView.visibleCells.count // wrong count oO
What's wrong here ?
这里有什么问题?
Like any view, a table view reloads its content lazily. Actually, if you call reloadData
multiple times it won't create performance issues. The table view only recomputes its content size based on its delegate implementation and waits the moment 3 to loads its cells. This time is called a layout pass.
像任何视图一样,表视图会懒惰地重新加载其内容。实际上,如果您reloadData
多次调用它不会造成性能问题。表格视图仅根据其委托实现重新计算其内容大小,并等待时刻 3 加载其单元格。这次称为布局传递。
Okay, how to get into the layout pass ?
好的,如何进入布局通行证?
During the layout pass, the app computes all the frames of the view hierarchy. To get involved, you can override the dedicated methods layoutSubviews
, updateLayoutConstraints
etc in a UIView
and the equivalent methods in a view controller subclass.
在布局过程中,应用程序计算视图层次结构的所有帧。涉足,您可以覆盖专用的方法layoutSubviews
,updateLayoutConstraints
等在UIView
与等效的方法在一个视图控制器子类。
That's exactly what a table view does. It overrides layoutSubviews
and based on your delegate implementation adds or removes cells. It calls cellForRow
right before adding and laying out a new cell, willDisplay
right after. If you called reloadData
or just added the table view to the hierarchy, the tables view adds as many cells as necessary to fill its frame at this key moment.
这正是表视图所做的。它覆盖layoutSubviews
并基于您的委托实现添加或删除单元格。它cellForRow
在添加和布置新单元格之前立即调用,之后立即调用willDisplay
。如果您调用reloadData
或刚刚将表格视图添加到层次结构中,表格视图会根据需要添加尽可能多的单元格以在关键时刻填充其框架。
Alright, but now, how to know when a tables view has finished reloading its content ?
好的,但是现在,如何知道表视图何时完成重新加载其内容?
We can now rephrase this question: how to know when a table view has finished laying out its subviews ?
我们现在可以重新表述这个问题:如何知道表视图何时完成其子视图的布局?
? The easiest way is to get into the layout of the table view :
? 最简单的方法是进入表格视图的布局:
class MyTableView: UITableView {
func layoutSubviews() {
super.layoutSubviews()
// the displayed cells are loaded
}
}
Note that this method is called many times in the life cycle of the table view. Because of the scroll and the dequeue behavior of the table view, cells are modified, removed and added often. But it works, right after the super.layoutSubviews()
, cells are loaded. This solution is equivalent to wait the willDisplay
event of the last index path. This event is called during the execution of layoutSubviews
of the table view for each cells added.
注意这个方法在表视图的生命周期中会被多次调用。由于表格视图的滚动和出队行为,单元格经常被修改、删除和添加。但它的工作原理super.layoutSubviews()
是在, 单元格加载之后。这个方案相当于等待willDisplay
最后一个索引路径的事件。在layoutSubviews
为每个添加的单元格执行表视图期间调用此事件。
? Another way is to get called when the app finishes a layout pass.
? 另一种方法是在应用程序完成布局传递时被调用。
As described in the documentation, you can use an option of the UIView.animate(withDuration:completion)
:
如文档中所述,您可以使用以下选项UIView.animate(withDuration:completion)
:
tableView.reloadData()
UIView.animate(withDuration: 0) {
// layout done
}
This solution works but the screen will refresh once between the time the layout is done and the time the block get called. This is equivalent to the DispatchMain.async
solution but specified.
此解决方案有效,但屏幕将在布局完成和块被调用之间刷新一次。这等效于DispatchMain.async
解决方案,但已指定。
? Alternatively, I would prefer to force the layout of the table view
? 或者,我更愿意强制表格视图的布局
There is a dedicated method to force any view to compute immediately its subview frames layoutIfNeeded
:
有一种专用方法可以强制任何视图立即计算其子视图帧layoutIfNeeded
:
tableView.reloadData()
table.layoutIfNeeded()
// layout done
Be careful however, doing so will remove the lazy loading used by the system. Calling those methods repeatedly could create performance issues. Make sure that they won't get called before the frame of the table view is computed, otherwise the table view will be loaded again and you won't be notified.
但是要小心,这样做会删除系统使用的延迟加载。重复调用这些方法可能会产生性能问题。确保在计算 table view 的 frame 之前不会调用它们,否则 table view 将再次加载并且您不会收到通知。
I think there is no perfect solution. Subclassing classes could lead to trubles. A layout pass starts from the top and goes to the bottom so it's not easy to get notified when all the layout is done. And layoutIfNeeded()
could create performance issues etc But knowing those options, you should be able to think at one alternative that will fit your need.
我认为没有完美的解决方案。子类化可能会导致问题。布局过程从顶部开始一直到底部,因此在所有布局完成后不容易得到通知。并且layoutIfNeeded()
可能会产生性能问题等但是了解这些选项后,您应该能够考虑一种适合您需求的替代方案。
回答by Ondrej Kvasnovsky
Here is how you do it in Swift 3:
以下是您在 Swift 3 中的做法:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
// perform your logic here, for the first row in the table
}
// ....
}