【发布时间】:2012-12-10 19:09:06
【问题描述】:
每当 UICollectionView 完全加载时,我都必须进行一些操作,即那时应该调用所有 UICollectionView 的数据源/布局方法。我怎么知道??是否有任何委托方法可以知道 UICollectionView 加载状态?
【问题讨论】:
标签: uicollectionview delegates reload
每当 UICollectionView 完全加载时,我都必须进行一些操作,即那时应该调用所有 UICollectionView 的数据源/布局方法。我怎么知道??是否有任何委托方法可以知道 UICollectionView 加载状态?
【问题讨论】:
标签: uicollectionview delegates reload
由于UICollectionView 的异步特性令人困惑,这里的大多数解决方案都不可靠或具有不确定的行为(这可能会导致随机错误)。
一个可靠的解决方案是继承UICollectionView 以在layoutSubviews() 的末尾运行一个完成块。
Objectice-C 中的代码: https://stackoverflow.com/a/39648633
Swift 中的代码: https://stackoverflow.com/a/39798079
【讨论】:
这对我有用:
[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{}
completion:^(BOOL finished) {
/// collection-view finished reload
}];
Swift 4 语法:
collectionView.reloadData()
collectionView.performBatchUpdates(nil, completion: {
(result) in
// ready
})
【讨论】:
reloadData 刷新了collectionView,因此它再次利用了它的数据源方法...performBatchUpdates 附加了一个完成块,因此如果两者都在主线程上执行,您知道任何代替@ 的代码987654326@ 将执行一个新的布局 collectionView
到目前为止我发现的最佳解决方案是使用CATransaction 来处理完成。
Swift 5:
CATransaction.begin()
CATransaction.setCompletionBlock {
// UICollectionView is ready
}
collectionView.reloadData()
CATransaction.commit()
更新: 上述解决方案似乎在某些情况下有效,而在某些情况下则无效。我最终使用了公认的答案,这绝对是最稳定和经过验证的方法。这是 Swift 5 版本:
private var contentSizeObservation: NSKeyValueObservation?
contentSizeObservation = collectionView.observe(\.contentSize) { [weak self] _, _ in
self?.contentSizeObservation = nil
completion()
}
collectionView.reloadData()
【讨论】:
SWIFT 5
override func viewDidLoad() {
super.viewDidLoad()
// "collectionViewDidLoad" for transitioning from product's cartView to it's cell in that view
self.collectionView?.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let observedObject = object as? UICollectionView, observedObject == self.collectionView {
print("collectionViewDidLoad")
self.collectionView?.removeObserver(self, forKeyPath: "contentSize")
}
}
【讨论】:
以下是唯一对我有用的方法。
extension UICollectionView {
func reloadData(_ completion: (() -> Void)? = nil) {
reloadData()
guard let completion = completion else { return }
layoutIfNeeded()
completion()
}
}
【讨论】:
我只是在重新加载集合视图后执行以下操作。您甚至可以在 API 响应中使用此代码。
self.collectionView.reloadData()
DispatchQueue.main.async {
// Do Task after collection view is reloaded
}
【讨论】:
只需在批量更新中重新加载collectionView,然后在布尔“完成”的帮助下检查完成块是否完成。
self.collectionView.performBatchUpdates({
self.collectionView.reloadData()
}) { (finish) in
if finish{
// Do your stuff here!
}
}
【讨论】:
尝试在 reloadData() 调用之后立即通过 layoutIfNeeded() 强制同步布局传递。似乎适用于 iOS 12 上的 UICollectionView 和 UITableView。
collectionView.reloadData()
collectionView.layoutIfNeeded()
// cellForItem/sizeForItem calls should be complete
completion?()
【讨论】:
这对我有用:
- (void)viewDidLoad {
[super viewDidLoad];
int ScrollToIndex = 4;
[self.UICollectionView performBatchUpdates:^{}
completion:^(BOOL finished) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:ScrollToIndex inSection:0];
[self.UICollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}];
}
【讨论】:
使用 RxSwift/RxCocoa 的不同方法:
collectionView.rx.observe(CGSize.self, "contentSize")
.subscribe(onNext: { size in
print(size as Any)
})
.disposed(by: disposeBag)
【讨论】:
performBatchUpdates 在我的情况下不起作用,这个可以。干杯!
正如dezinezync 回答的那样,您需要从UITableView 或UICollectionView 将reloadData 之后的代码块分派到主队列,然后在单元格出列后执行此块
为了在使用时更直接,我会使用这样的扩展:
extension UICollectionView {
func reloadData(_ completion: @escaping () -> Void) {
reloadData()
DispatchQueue.main.async { completion() }
}
}
也可以实现为UITableView
【讨论】:
当集合视图在用户可见之前被加载时,我需要对所有可见单元格执行一些操作,我使用了:
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if shouldPerformBatch {
self.collectionView.performBatchUpdates(nil) { completed in
self.modifyVisibleCells()
}
}
}
注意,这将在滚动浏览集合视图时被调用,所以为了防止这种开销,我添加了:
private var souldPerformAction: Bool = true
在动作本身中:
private func modifyVisibleCells() {
if self.shouldPerformAction {
// perform action
...
...
}
self.shouldPerformAction = false
}
该动作仍会执行多次,作为初始状态下可见单元格的数量。但是在所有这些调用中,您将拥有相同数量的可见单元格(全部)。并且布尔标志将阻止它在用户开始与集合视图交互后再次运行。
【讨论】:
默认这样做:
//Subclass UICollectionView
class MyCollectionView: UICollectionView {
//Store a completion block as a property
var completion: (() -> Void)?
//Make a custom funciton to reload data with a completion handle
func reloadData(completion: @escaping() -> Void) {
//Set the completion handle to the stored property
self.completion = completion
//Call super
super.reloadData()
}
//Override layoutSubviews
override func layoutSubviews() {
//Call super
super.layoutSubviews()
//Call the completion
self.completion?()
//Set the completion to nil so it is reset and doesn't keep gettign called
self.completion = nil
}
}
然后在你的 VC 中这样调用
let collection = MyCollectionView()
self.collection.reloadData(completion: {
})
确保您使用的是子类!!
【讨论】:
只是为了添加一个很棒的@dezinezync 答案:
斯威夫特 3+
collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
// your stuff here executing after collectionView has been layouted
}
【讨论】:
这就是我解决 Swift 3.0 问题的方法:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.collectionView.visibleCells.isEmpty {
// stuff
}
}
【讨论】:
这样做:
UIView.animateWithDuration(0.0, animations: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.collectionView.reloadData()
}, completion: { [weak self] (finished) in
guard let strongSelf = self else { return }
// Do whatever is needed, reload is finished here
// e.g. scrollToItemAtIndexPath
let newIndexPath = NSIndexPath(forItem: 1, inSection: 0)
strongSelf.collectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
})
【讨论】:
这对我有用:
__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
[wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
[wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];
【讨论】:
// In viewDidLoad
[self.collectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// You will get here when the reloadData finished
}
- (void)dealloc
{
[self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
}
【讨论】:
[self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL]; 添加到-viewWillDisappear:animated:。
其实很简单。
例如,当您调用 UICollectionView 的 reloadData 方法或它的布局的 invalidateLayout 方法时,您可以执行以下操作:
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
dispatch_async(dispatch_get_main_queue(), ^{
//your stuff happens here
//after the reloadData/invalidateLayout finishes executing
});
为什么会这样:
主线程(我们应该在其中进行所有 UI 更新)包含主队列,它本质上是串行的,即它以 FIFO 方式工作。所以在上面的例子中,第一个块被调用,我们的 reloadData 方法被调用,然后是第二个块中的任何其他内容。
现在主线程也被阻塞了。因此,如果您是 reloadData 需要 3s 执行,则第二个块的处理将被这些 3s 延迟。
【讨论】:
reloadData 现在将单元格的加载排入主线程(即使从主线程调用)。所以在reloadData 返回后,单元格实际上并没有被加载(cellForRowAtIndexPath: 没有被调用)。在将单元格加载到 dispatch_async(dispatch_get_main... 后包装您要执行的代码,并在您调用 reloadData 之后调用该代码将获得所需的结果。
试试这个:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return _Items.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell;
//Some cell stuff here...
if(indexPath.row == _Items.count-1){
//THIS IS THE LAST CELL, SO TABLE IS LOADED! DO STUFF!
}
return cell;
}
【讨论】:
你可以这样做......
- (void)reloadMyCollectionView{
[myCollectionView reload];
[self performSelector:@selector(myStuff) withObject:nil afterDelay:0.0];
}
- (void)myStuff{
// Do your stuff here. This will method will get called once your collection view get loaded.
}
【讨论】: