我想在加载完成后更改表的偏移量,该偏移量取决于表上加载的单元格数。
无论如何,SDK上是否知道uitableview加载何时完成?我既没有看到委托,也没有看到数据源协议。
我无法使用数据源的计数,因为仅可见单元的加载。
我想在加载完成后更改表的偏移量,该偏移量取决于表上加载的单元格数。
无论如何,SDK上是否知道uitableview加载何时完成?我既没有看到委托,也没有看到数据源协议。
我无法使用数据源的计数,因为仅可见单元的加载。
Answers:
改进为@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];
}
}
更新:
嗯,@ htafoya的评论是正确的。如果您希望此代码检测从源加载所有数据的结束,则不会,但这不是原始问题。此代码用于检测何时显示所有要显示的单元格。willDisplayCell:
用于更流畅的用户界面(单个单元格通常在willDisplay:
调用后显示很快)。您也可以尝试使用tableView:didEndDisplayingCell:
。
viewForFooterInSection
方法中应用相同的想法。
Swift 3&4&5版本:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let lastVisibleIndexPath = tableView.indexPathsForVisibleRows?.last {
if indexPath == lastVisibleIndexPath {
// do here...
}
}
}
我总是使用这个非常简单的解决方案:
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath row] == lastRow){
//end of loading
//for example [activityIndicator stopAnimating];
}
}
[tableView numberOfRowsInSection: 0] - 1
。您必须替换0
为所需的值。但这不是问题。问题是UITableView仅加载可见的。但是((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row
解决了问题。
这是另一个似乎对我有用的选择。在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");
}
如果您要查找整个UITableView
而不是可见单元的最终负载,我发现这种方法最有效。根据您的需要,您可能只需要可见的单元格,在这种情况下,folex的答案是一个不错的选择。
nil
从tableView:viewForFooterInSection:
iOS 7中的布局弄乱了返回。
return CGRectMake(0,0,0,0);
self.tableView.sectionFooterHeight = 0
。无论哪种方式,似乎都插入了一个高度大约为10的部分页脚视图。我敢打赌,可以通过在返回的视图中添加0高度约束来解决此问题。无论如何,我很好,因为我实际上想弄清楚如何在最后一个单元格上启动UITableView,但首先看到了这一点。
Swift 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()
}
}
使用私有API:
@objc func tableViewDidFinishReload(_ tableView: UITableView) {
print(#function)
cellsAreLoaded = true
}
使用公共API:
- (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.myDataSource numberOfRowsInSection:section];
}
// called after the rows in the last section is loaded
-(void)tableViewDidLoadRows:(UITableView*)tableView{
self.cellsAreLoaded = YES;
}
可能的更好设计是将可见单元格添加到集合中,然后当您需要检查表是否已加载时,可以在该集合周围执行for循环,例如
var visibleCells = Set<UITableViewCell>()
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
visibleCells.insert(cell)
}
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
visibleCells.remove(cell)
}
// example property you want to show on a cell that you only want to update the cell after the table is loaded. cellForRow also calls configure too for the initial state.
var count = 5 {
didSet {
for cell in visibleCells {
configureCell(cell)
}
}
}
对于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
}
}
}
我需要isLoadingTableView变量,因为我想确保在完成默认单元格选择之前已完成表的加载。如果您不包括它,那么每次滚动表时,它将再次调用您的代码。
我知道最好的方法是Eric的回答:UITableView完成数据查询后得到通知?
更新:为了使其正常工作,我必须将这些呼叫放入 -tableView:cellForRowAtIndexPath:
[tableView beginUpdates];
[tableView endUpdates];
要了解表格视图何时完成其内容加载,我们首先需要对如何将视图放置在屏幕上有基本的了解。
在应用程序的生命周期中,有4个关键时刻:
2次和3次完全分开。为什么呢 出于性能原因,我们不希望每次进行修改时都执行时刻3的所有计算。
所以,我认为您正面临这样的情况:
tableView.reloadData()
tableView.visibleCells.count // wrong count oO
这是怎么了
像任何视图一样,表视图会延迟加载其内容。实际上,如果您reloadData
多次拨打电话,则不会造成性能问题。表格视图仅基于其委托实现重新计算其内容大小,并等待3时刻加载其单元格。这次称为布局通行证。
好的,如何进入布局通行证?
在布局过程中,应用程序将计算视图层次结构的所有框架。涉足,您可以覆盖专用的方法layoutSubviews
,updateLayoutConstraints
等在UIView
与等效的方法在一个视图控制器子类。
这正是表视图的功能。它重写layoutSubviews
并根据您的委托实现添加或删除单元格。它会cellForRow
在添加和布局新单元之前立即调用willDisplay
。如果您调用reloadData
了表格视图或只是将表格视图添加到层次结构中,则表格视图会在此关键时刻添加必要数量的单元格以填充其框架。
好的,但是现在,如何知道表视图何时完成了其内容的重新加载?
现在,我们可以重新表述这个问题:如何知道表视图何时完成其子视图的布局?
• 最简单的方法是进入表格视图的布局:
class MyTableView: UITableView {
func layoutSubviews() {
super.layoutSubviews()
// the displayed cells are loaded
}
}
请注意,在表视图的生命周期中多次调用此方法。由于表格视图的滚动和出队行为,经常修改,删除和添加单元格。但是,在super.layoutSubviews()
加载了单元格之后,它可以正常工作。此解决方案等效于等待willDisplay
最后一个索引路径的事件。layoutSubviews
在表视图的执行期间,为添加的每个单元格调用此事件。
• 另一种方法是在应用程序完成布局传递时被调用。
如文档中所述,您可以使用以下选项UIView.animate(withDuration:completion)
:
tableView.reloadData()
UIView.animate(withDuration: 0) {
// layout done
}
此解决方案有效,但是在完成布局和调用块之间,屏幕将刷新一次。这等效于DispatchMain.async
解决方案,但已指定。
• 或者,我希望强制使用表格视图的布局
有一种专用方法可以强制任何视图立即计算其子视图帧layoutIfNeeded
:
tableView.reloadData()
table.layoutIfNeeded()
// layout done
但是请注意,这样做会删除系统使用的延迟加载。反复调用这些方法可能会导致性能问题。在计算表格视图的框架之前,请确保它们不会被调用,否则表格视图将再次加载,并且不会收到通知。
我认为没有完美的解决方案。子类化可能导致混乱。布局遍历从顶部开始一直到达底部,因此在完成所有布局后不容易得到通知。并layoutIfNeeded()
可能导致性能问题等,但是了解这些选择,您应该能够想到一种适合您需要的选择。
这是您在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
}
// ....
}
这是我在Swift 3中的操作方法
let threshold: CGFloat = 76.0 // threshold from bottom of tableView
internal func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
if (!isLoadingMore) && (maximumOffset - contentOffset <= threshold) {
self.loadVideosList()
}
}
这就是我要做的。
在您的基类中(可以是rootVC BaseVc等),
A.编写一个协议以发送“ DidFinishReloading”回调。
@protocol ReloadComplition <NSObject>
@required
- (void)didEndReloading:(UITableView *)tableView;
@end
B.编写一个通用方法来重新加载表视图。
-(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController;
在基类方法实现中,先调用reloadData,再调用带有延迟的委托方法。
-(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[tableView reloadData];
if(aViewController && [aViewController respondsToSelector:@selector(didEndReloading:)]){
[aViewController performSelector:@selector(didEndReloading:) withObject:tableView afterDelay:0];
}
}];
}
在需要回调的所有视图控制器中确认重新加载完成协议。
-(void)didEndReloading:(UITableView *)tableView{
//do your stuff.
}
参考:https : //discussions.apple.com/thread/2598339?start=0&tstart=0
@folex的答案是正确的。
但是,如果tableView 一次显示多个部分,它将失败。
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath isEqual:((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject])]){
//end of loading
}
}
我知道已回答,我只是添加一条建议。
根据以下文档
https://www.objc.io/issues/2-concurrency/thread-safe-class-design/
使用dispatch_async解决时序问题是一个坏主意。我建议我们应该通过添加FLAG或其他方式来解决此问题。
如果您有多个部分,请按以下步骤获取最后部分中的最后一行(快速键3):
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let visibleRows = tableView.indexPathsForVisibleRows, let lastRow = visibleRows.last?.row, let lastSection = visibleRows.map({$0.section}).last {
if indexPath.row == lastRow && indexPath.section == lastSection {
// Finished loading visible rows
}
}
}
我正在复制安德鲁的代码,并对其进行扩展以解决表中只有1行的情况。到目前为止,对我来说一直有效!
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// detect when all visible cells have been loaded and displayed
// NOTE: iOS7 workaround used - see: http://stackoverflow.com/questions/4163579/how-to-detect-the-end-of-loading-of-uitableview?lq=1
NSArray *visibleRows = [tableView indexPathsForVisibleRows];
NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject];
BOOL isPreviousCallForPreviousCell = self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row;
BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath];
BOOL isFinishedLoadingTableView = isLastCell && ([tableView numberOfRowsInSection:0] == 1 || isPreviousCallForPreviousCell);
self.previousDisplayedIndexPath = indexPath;
if (isFinishedLoadingTableView) {
[self hideSpinner];
}
}
注意:我只是使用Andrew代码中的1部分,因此请记住这一点。
在iOS7.0x中,解决方案有所不同。这是我想出的。
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
BOOL isFinishedLoadingTableView = [self isFinishedLoadingTableView:tableView
indexPath:indexPath];
if (isFinishedLoadingTableView) {
NSLog(@"end loading");
}
}
- (BOOL)isFinishedLoadingTableView:(UITableView *)tableView
indexPath:(NSIndexPath *)indexPath
{
// The reason we cannot just look for the last row is because
// in iOS7.0x the last row is updated before
// looping through all the visible rows in ascending order
// including the last row again. Strange but true.
NSArray * visibleRows = [tableView indexPathsForVisibleRows]; // did verify sorted ascending via logging
NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject];
// For tableviews with multiple sections this will be more complicated.
BOOL isPreviousCallForPreviousCell =
self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row;
BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath];
BOOL isFinishedLoadingTableView = isLastCell && isPreviousCallForPreviousCell;
self.previousDisplayedIndexPath = indexPath;
return isFinishedLoadingTableView;
}
目标C
[self.tableView reloadData];
[self.tableView performBatchUpdates:^{}
completion:^(BOOL finished) {
/// table-view finished reload
}];
迅速
self.tableView?.reloadData()
self.tableView?.performBatchUpdates({ () -> Void in
}, completion: { (Bool finished) -> Void in
/// table-view finished reload
})
UICollectionView
据我了解,此方法仅可用。