我试图滚动到UITableView的底部后,它完成执行[自我。tableView reloadData]。
我原本有
[self.tableView reloadData]
NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
但后来我读到reloadData是异步的,所以自self以来滚动就没有发生。表视图,自我。tableView numberOfSections和[self.]tableView numberOfRowsinSection都是0。
很奇怪,我用的是:
[self.tableView reloadData];
NSLog(@"Number of Sections %d", [self.tableView numberOfSections]);
NSLog(@"Number of Rows %d", [self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1);
在控制台中,它返回Sections = 1, Row = -1;
当我在cellForRowAtIndexPath做完全相同的nslog时,我得到Sections = 1和Row = 8;(8是正确的)
上面的dispatch_async(dispatch_get_main_queue())方法不能保证有效。我看到了它的非确定性行为,其中有时系统在完成块之前完成了layoutSubviews和单元格渲染,有时在完成块之后。
这里有一个解决方案,100%适用于我,在iOS 10上。它需要实例化UITableView或UICollectionView作为自定义子类的能力。这是UICollectionView的解决方案,但它对UITableView是完全一样的:
CustomCollectionView.h:
#import <UIKit/UIKit.h>
@interface CustomCollectionView: UICollectionView
- (void)reloadDataWithCompletion:(void (^)(void))completionBlock;
@end
CustomCollectionView.m:
#import "CustomCollectionView.h"
@interface CustomCollectionView ()
@property (nonatomic, copy) void (^reloadDataCompletionBlock)(void);
@end
@implementation CustomCollectionView
- (void)reloadDataWithCompletion:(void (^)(void))completionBlock
{
self.reloadDataCompletionBlock = completionBlock;
[self reloadData];
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.reloadDataCompletionBlock) {
self.reloadDataCompletionBlock();
self.reloadDataCompletionBlock = nil;
}
}
@end
使用示例:
[self.collectionView reloadDataWithCompletion:^{
// reloadData is guaranteed to have completed
}];
请看这个答案的Swift版本
和一个UICollectionView版本,基于kolaworld的答案。
它需要测试。到目前为止,它在iOS 9.2和Xcode 9.2 beta 2上工作,将collectionView滚动到一个索引,作为一个闭包。
extension UICollectionView
{
/// Calls reloadsData() on self, and ensures that the given closure is
/// called after reloadData() has been completed.
///
/// Discussion: reloadData() appears to be asynchronous. i.e. the
/// reloading actually happens during the next layout pass. So, doing
/// things like scrolling the collectionView immediately after a
/// call to reloadData() can cause trouble.
///
/// This method uses CATransaction to schedule the closure.
func reloadDataThenPerform(_ closure: @escaping (() -> Void))
{
CATransaction.begin()
CATransaction.setCompletionBlock(closure)
self.reloadData()
CATransaction.commit()
}
}
用法:
myCollectionView.reloadDataThenPerform {
myCollectionView.scrollToItem(at: indexPath,
at: .centeredVertically,
animated: true)
}
当[tableView reloadData]返回时,tableView背后的内部数据结构已经被更新。因此,当方法完成时,您可以安全地滚动到底部。我在自己的应用程序中验证了这一点。rob mayoff的答案被广泛接受,尽管术语也令人困惑,但他在最近的更新中承认了这一点。
如果你的tableView没有滚动到底部,你可能在你没有发布的其他代码中有问题。也许在滚动完成后,您正在更改数据,并且没有重新加载和/或滚动到底部?
添加如下的日志记录,以验证reloadData之后的表数据是否正确。我在一个示例应用程序中有以下代码,它可以完美地工作。
// Change the data source
NSLog(@"Before reload / sections = %d, last row = %d",
[self.tableView numberOfSections],
[self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);
[self.tableView reloadData];
NSLog(@"After reload / sections = %d, last row = %d",
[self.tableView numberOfSections],
[self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]-1
inSection:[self.tableView numberOfSections] - 1]
atScrollPosition:UITableViewScrollPositionBottom
animated:YES];