UICollectionViewが完全にロードされたときはいつでも、何らかの操作を行う必要があります。つまり、その時点で、UICollectionViewのすべてのデータソース/レイアウトメソッドを呼び出す必要があります。どうすればそれを知ることができますか?UICollectionViewのロードステータスを知るためのデリゲートメソッドはありますか?
21 に答える
これは私のために働いた:
[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{}
completion:^(BOOL finished) {
/// collection-view finished reload
}];
Swift 4構文:
collectionView.reloadData()
collectionView.performBatchUpdates(nil, completion: {
(result) in
// ready
})
// 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];
}
それは実際にはかなり単純です。
たとえば、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秒によって延期されます。
素晴らしい@dezinezyncの答えに追加するだけです:
Swift 3+
collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
// your stuff here executing after collectionView has been layouted
}
RxSwift / RxCocoaを使用した別のアプローチ:
collectionView.rx.observe(CGSize.self, "contentSize")
.subscribe(onNext: { size in
print(size as Any)
})
.disposed(by: disposeBag)
このようにしてください:
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
ます
reloadData()呼び出しの直後にlayoutIfNeeded()を介して同期レイアウトパスを強制してみてください。iOS12のUICollectionViewとUITableViewの両方で機能するようです。
collectionView.reloadData()
collectionView.layoutIfNeeded()
// cellForItem/sizeForItem calls should be complete
completion?()
コレクションビューがリロードされた後、何かを実行するために次のことを実行しました。このコードは、API応答でも使用できます。
self.collectionView.reloadData()
DispatchQueue.main.async {
// Do Task after collection view is reloaded
}
私がこれまでに見つけた最善の解決策は、CATransaction
完了を処理するために使用することです。
スウィフト5:
CATransaction.begin()
CATransaction.setCompletionBlock {
// UICollectionView is ready
}
collectionView.reloadData()
CATransaction.commit()
更新:上記の解決策は、場合によっては機能するように見えますが、機能しない場合もあります。私は受け入れられた答えを使用することになりました、そしてそれは間違いなく最も安定して証明された方法です。Swift5バージョンは次のとおりです。
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")
}
}
これは私のために働きます:
__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
[wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
[wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];
コレクションビューがユーザーに表示される前に読み込まれたときに、表示されているすべてのセルに対して何らかのアクションを実行する必要がありました。次のように使用しました。
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
}
初期状態で表示されているセルの数として、アクションは引き続き複数回実行されます。ただし、これらすべての呼び出しで、同じ数の表示セル(すべて)が表示されます。また、ブールフラグは、ユーザーがコレクションビューを操作し始めた後、再び実行されないようにします。
バッチ更新内でcollectionViewをリロードし、ブール値の「finish」を使用して完了ブロックをチェックインするだけです。
self.collectionView.performBatchUpdates({
self.collectionView.reloadData()
}) { (finish) in
if finish{
// Do your stuff here!
}
}
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: {
})
サブクラスを使用していることを確認してください!!
私のためのこの仕事:
- (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];
}];
}
以下は私のために働いた唯一のアプローチです。
extension UICollectionView {
func reloadData(_ completion: (() -> Void)? = nil) {
reloadData()
guard let completion = completion else { return }
layoutIfNeeded()
completion()
}
}
ここでのソリューションのほとんどは、の紛らわしい非同期性のために、信頼性がないか、非決定論的な動作(ランダムなバグを引き起こす可能性があります)を持っていますUICollectionView
。
UICollectionView
信頼できる解決策は、の最後に完了ブロックを実行するようにサブクラス化することですlayoutSubviews()
。
Objectice-Cのコード: https ://stackoverflow.com/a/39648633
Swiftのコード: https ://stackoverflow.com/a/39798079
これが私がSwift3.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.
}