web-dev-qa-db-ja.com

UICollectionViewが完全にロードされたことを確認するにはどうすればよいですか?

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

87
Jirune
// 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];
}
59
Cullen SUN

これは私のために働いた:

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

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

たとえば、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秒遅延されます。

24
dezinezync

素晴らしい@dezinezyncの答えに追加するだけです:

Swift 3+

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

次のようにします:

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

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

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

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

collectionView.reloadData()
collectionView.layoutIfNeeded() 

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

これは私のために働く:

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

dezinezync と答えたので、必要なのは、reloadDataまたはUITableViewからUICollectionViewの後のコードブロックをメインキューにディスパッチすることです。セルのデキュー後に実行される

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

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

UITableViewにも実装できます

1
Matheus Rocco

私のためのこの仕事:


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

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

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
}

アクションは、初期状態の可視セルの数として、引き続き複数回実行されます。ただし、これらの呼び出しのすべてで、同じ数の可視セル(すべて)があります。また、ブールフラグは、ユーザーがコレクションビューとの対話を開始した後、ブールフラグが再度実行されるのを防ぎます。

0
gutte

Defこれを行います:

//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
Jon Vogel