UICollectionViewが完全にロードされたことをどのように知ることができますか?


98

UICollectionViewが完全に読み込まれるたびに何らかの操作を行う必要があります。つまり、その時点ですべてのUICollectionViewのデータソース/レイアウトメソッドを呼び出す必要があります。どうやってそれを知るのですか?UICollectionViewのロード状態を知るためのデリゲートメソッドはありますか?

回答:


62
// 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];
}

1
これは、戻るときにクラッシュしますが、それ以外の場合はうまく機能します。
ericosg 2015

4
@ericosgも追加[self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];-viewWillDisappear:animated:ます。
pxpgraphics

鮮やかさ!どうもありがとうございました!
Abdo

41
スクロールするとコンテンツのサイズも変化するため、信頼性がありません。
Ryan Heitner、2015

非同期のRESTメソッドを使用してデータを取得するコレクションビューを試しました。これは、このメソッドが起動するとき、Visible Cellsが常に空であることを意味します。つまり、最初にコレクション内のアイテムにスクロールします。
チャッキー2016

155

これは私のために働きました:

[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
})

1
iOS 9で私のために働いた
Magoo

5
@ G.Abhisek Errrr、確かに....あなたは何を説明する必要がありますか?最初の行はをreloadData更新して、collectionViewデータソースメソッドを再度描画します... performBatchUpdates完了ブロックがアタッチされているので、メインスレッドで両方が実行された場合、代わりに配置されたコードが/// collection-view finished reload新しくレイアウトされて実行されることがわかりますcollectionView
Magoo

8
collectionViewに動的サイズのセルがある場合、私には機能しません
インガハム

2
この方法は、頭痛のない完璧なソリューションです。
arjavlad 2017年

1
@ingahamこれは、動的サイズのセル、Swift 4.2 IOS 12
Romulo BMで

27

実際にはかなり単純です。

たとえば、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秒によって延期されます。


2
実際にこれをテストしたところ、他のすべてのデリゲートメソッドの前にブロックが呼び出されています。それでうまくいきませんか?
taylorcressy 2014

@taylorcressyこんにちは、コードを更新しました。これは、現在2つのプロダクションアプリで使用しているものです。説明も含まれています。
dezinezync 2014

うまくいきません。reloadDataを呼び出すと、2番目のブロックの実行後に、collectionViewがセルを要求します。だから私は@taylorcressyが持っているように見えるのと同じ問題を抱えています。
ラファエル

1
最初のブロック呼び出しの中に2番目のブロック呼び出しを入れてみてください。未確認ですが、機能する可能性があります。
dezinezync 2014

6
reloadData(メインスレッドから呼び出された場合でも)メインスレッドのセルのロードをキューに入れるようになりました。したがって、セルは戻ったcellForRowAtIndexPath:後に実際にはロードされません(呼び出されません)reloadData。セルがロードされた後に実行するコードをラップしdispatch_async(dispatch_get_main...、呼び出し後にそれを呼び出すとreloadData、目的の結果が得られます。
Tylerc230 2016年

12

素晴らしい@dezinezync回答に追加するには:

Swift 3以上

collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
    // your stuff here executing after collectionView has been layouted
}

@nikasこれはビューに追加する必要がありますが表示されました!!
SARATH SASI 2017

1
必ずしもそうとは限りませんが、レイアウトが最終的に読み込まれたときに、何かを実行したい場所から呼び出すことができます。
nikans

アイテムを挿入した後、collectionViewを最後にスクロールしようとするとスクロールフリッカーが発生していました。これで修正されました。
Skoua

6

このようにしてください:

       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)
        })

3

以下のようdezinezyncが答え、何が必要なのはメインキュー後のコードのブロックへの発送にあるreloadDataからUITableViewUICollectionView、その後、このブロックは、デキュー細胞の後に実行されます

使用時にこれをより簡単にするために、次のような拡張機能を使用します。

extension UICollectionView {
    func reloadData(_ completion: @escaping () -> Void) {
        reloadData()
        DispatchQueue.main.async { completion() }
    }
}

それはまたに実装することができUITableView、同様


3

RxSwift / RxCocoaを使用した別のアプローチ:

        collectionView.rx.observe(CGSize.self, "contentSize")
            .subscribe(onNext: { size in
                print(size as Any)
            })
            .disposed(by: disposeBag)

1
私はこれが好きです。performBatchUpdates私の場合は機能していませんでしたが、これは機能します。乾杯!
ゾルタン

@MichałZiobro、どこかでコードを更新できますか?このメソッドは、contentSizeが変更されたときにのみ呼び出す必要がありますか?したがって、スクロールごとに変更しない限り、これは機能するはずです!
Chinh Nguyen

3

reloadData()呼び出しの直後に、layoutIfNeeded()を介して同期レイアウトパスを強制してみてください。iOS 12のUICollectionViewとUITableViewの両方で機能するようです。

collectionView.reloadData()
collectionView.layoutIfNeeded() 

// cellForItem/sizeForItem calls should be complete
completion?()

ありがとう!私は長い間、その問題の解決策を探していました。
Trzy Gracje

1

これは私にとってはうまくいきます:

__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
    [wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
    [wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];

2
ナンセンス...これはレイアウトをまだ完了していません。
Magoo

1

ユーザーに表示される前にコレクションビューが読み込まれると、すべての表示セルで何らかのアクションを実行する必要がありました。

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
}

アクションは、初期状態で表示されているセルの数として、複数回実行されます。しかし、これらのすべての呼び出しでは、同じ数の可視セル(すべて)が表示されます。また、ブールフラグを使用すると、ユーザーがコレクションビューの操作を開始した後に、ブールフラグが再度実行されるのを防ぎます。


1

コレクションビューがリロードされた後に何かを実行するために、私は次のことを行いました。このコードはAPIレスポンスでも使用できます。

self.collectionView.reloadData()

DispatchQueue.main.async {
   // Do Task after collection view is reloaded                
}

1

これまでに見つけた最良の解決策は、CATransaction完了を処理するために使用することです。

スウィフト5

CATransaction.begin()
CATransaction.setCompletionBlock {
    // UICollectionView is ready
}

collectionView.reloadData()

CATransaction.commit()

0

デフはこれを行います:

//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: {

})

サブクラスを使用していることを確認してください!!


0

私にとってこの作品:


- (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];
                                  }];

}


0

バッチ更新内でcollectionViewをリロードし、ブールの「finish」を使用して完了ブロックをチェックするだけです。

self.collectionView.performBatchUpdates({
        self.collectionView.reloadData()
    }) { (finish) in
        if finish{
            // Do your stuff here!
        }
    }

0

以下は私のために働いた唯一のアプローチです。

extension UICollectionView {
    func reloadData(_ completion: (() -> Void)? = nil) {
        reloadData()
        guard let completion = completion else { return }
        layoutIfNeeded()
        completion()
    }
}

-1

これは私がSwift 3.0の問題をどのように解決したかです:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if !self.collectionView.visibleCells.isEmpty {
        // stuff
    }
}

これは機能しません。VisibleCellsには​​、最初のセルが読み込まれるとすぐにコンテンツが含まれます...問題は、最後のセルが読み込まれたときに知りたいことです。
Travis

-9

これを試して:

- (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;
}

2
不正解です。cellForは、そのセルが表示される場合にのみ呼び出されます。その場合、最後に表示されたアイテムはアイテムの総数と同じではありません...
Jirune

-10

あなたはこのようにすることができます...

  - (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.

    }

このコードは質問に答えることがありますが、なぜまたはどのように質問に答えるに関する追加のコンテキストを提供すると、その長期的な価値が大幅に向上します。回答を編集して説明を追加してください。
Toby Speight 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.