我试图滚动到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版本
重载发生在下一个布局传递期间,通常发生在您将控制返回到运行循环时(例如,在您的按钮操作或任何返回之后)。
因此,在表视图重新加载后运行某些东西的一种方法是简单地强制表视图立即执行布局:
[self.tableView reloadData];
[self.tableView layoutIfNeeded];
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];
另一种方法是使用dispatch_async调度后布局代码:
[self.tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
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返回之前将tableView:numberOfSections:和tableView:numberOfRowsInSection:发送到它的数据源。如果委托实现了tableView: highightforrowatindexpath:,表视图也会在从reloadData返回之前发送它(对于每一行)。
然而,在布局阶段之前,表视图不会发送tableView:cellForRowAtIndexPath:或tableView:headerViewForSection,这是在您将控制返回到运行循环时默认发生的。
我还发现,在一个小的测试程序中,您的问题中的代码正确地滚动到表视图的底部,而不需要我做任何特殊的事情(如发送layoutIfNeeded或使用dispatch_async)。
我最终使用了Shawn解决方案的一个变种:
创建一个带有委托的自定义UITableView类:
protocol CustomTableViewDelegate {
func CustomTableViewDidLayoutSubviews()
}
class CustomTableView: UITableView {
var customDelegate: CustomTableViewDelegate?
override func layoutSubviews() {
super.layoutSubviews()
self.customDelegate?.CustomTableViewDidLayoutSubviews()
}
}
然后在我的代码中,我使用
class SomeClass: UIViewController, CustomTableViewDelegate {
@IBOutlet weak var myTableView: CustomTableView!
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.customDelegate = self
}
func CustomTableViewDidLayoutSubviews() {
print("didlayoutsubviews")
// Do other cool things here!!
}
}
同时确保在Interface Builder中将表视图设置为CustomTableView: