回答:
// 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:
ます。
これは私のために働きました:
[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{}
completion:^(BOOL finished) {
/// collection-view finished reload
}];
Swift 4の構文:
self.collectionView.reloadData()
self.collectionView.performBatchUpdates(nil, completion: {
(result) in
// ready
})
reloadData
更新して、collectionView
データソースメソッドを再度描画します... performBatchUpdates
完了ブロックがアタッチされているので、メインスレッドで両方が実行された場合、代わりに配置されたコードが/// collection-view finished reload
新しくレイアウトされて実行されることがわかりますcollectionView
実際にはかなり単純です。
たとえば、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
メソッドが呼び出され、その後に2番目のブロックの他のブロックが続きます。
現在、メインスレッドもブロックしています。したがって、reloadData
実行に3秒かかる場合、2番目のブロックの処理はそれらの3秒によって延期されます。
reloadData
(メインスレッドから呼び出された場合でも)メインスレッドのセルのロードをキューに入れるようになりました。したがって、セルは戻ったcellForRowAtIndexPath:
後に実際にはロードされません(呼び出されません)reloadData
。セルがロードされた後に実行するコードをラップしdispatch_async(dispatch_get_main...
、呼び出し後にそれを呼び出すとreloadData
、目的の結果が得られます。
素晴らしい@dezinezync回答に追加するには:
Swift 3以上
collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
// your stuff here executing after collectionView has been layouted
}
このようにしてください:
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)
})
以下のようdezinezyncが答え、何が必要なのはメインキュー後のコードのブロックへの発送にあるreloadData
からUITableView
かUICollectionView
、その後、このブロックは、デキュー細胞の後に実行されます
使用時にこれをより簡単にするために、次のような拡張機能を使用します。
extension UICollectionView {
func reloadData(_ completion: @escaping () -> Void) {
reloadData()
DispatchQueue.main.async { completion() }
}
}
それはまたに実装することができUITableView
、同様
RxSwift / RxCocoaを使用した別のアプローチ:
collectionView.rx.observe(CGSize.self, "contentSize")
.subscribe(onNext: { size in
print(size as Any)
})
.disposed(by: disposeBag)
performBatchUpdates
私の場合は機能していませんでしたが、これは機能します。乾杯!
reloadData()呼び出しの直後に、layoutIfNeeded()を介して同期レイアウトパスを強制してみてください。iOS 12のUICollectionViewとUITableViewの両方で機能するようです。
collectionView.reloadData()
collectionView.layoutIfNeeded()
// cellForItem/sizeForItem calls should be complete
completion?()
ユーザーに表示される前にコレクションビューが読み込まれると、すべての表示セルで何らかのアクションを実行する必要がありました。
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
}
アクションは、初期状態で表示されているセルの数として、複数回実行されます。しかし、これらのすべての呼び出しでは、同じ数の可視セル(すべて)が表示されます。また、ブールフラグを使用すると、ユーザーがコレクションビューの操作を開始した後に、ブールフラグが再度実行されるのを防ぎます。
これまでに見つけた最良の解決策は、CATransaction
完了を処理するために使用することです。
スウィフト5:
CATransaction.begin()
CATransaction.setCompletionBlock {
// UICollectionView is ready
}
collectionView.reloadData()
CATransaction.commit()
デフはこれを行います:
//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: {
})
サブクラスを使用していることを確認してください!!
私にとってこの作品:
- (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];
}];
}
これは私がSwift 3.0の問題をどのように解決したかです:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.collectionView.visibleCells.isEmpty {
// stuff
}
}
これを試して:
- (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.
}