Come faccio a sapere che UICollectionView è stato caricato completamente?


98

Devo fare qualche operazione ogni volta che UICollectionView è stato caricato completamente, cioè in quel momento dovrebbero essere chiamati tutti i metodi datasource / layout di UICollectionView. Come lo so? Esiste un metodo delegato per conoscere lo stato di caricamento di UICollectionView?

Risposte:


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
questo si blocca per me quando torno indietro, altrimenti funziona bene.
ericosg

4
@ericosg aggiungi [self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];a -viewWillDisappear:animated:pure.
pxpgraphics

Brillante! Grazie mille!
Abdo

41
La dimensione del contenuto cambia anche durante lo scorrimento, quindi non è affidabile.
Ryan Heitner

Ho provato per una visualizzazione raccolta che ottiene i suoi dati utilizzando un metodo di riposo asincrono. Ciò significa che le celle visibili sono sempre vuote quando questo metodo viene attivato, il che significa che scorro fino a un elemento nella raccolta all'inizio.
Chucky

155

Questo ha funzionato per me:

[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{}
                              completion:^(BOOL finished) {
                                  /// collection-view finished reload
                              }];

Sintassi di Swift 4:

self.collectionView.reloadData()
self.collectionView.performBatchUpdates(nil, completion: {
    (result) in
    // ready
})

1
Ha funzionato per me su iOS 9
Magoo

5
@ G.Abhisek Errrr, certo .... cosa hai bisogno di spiegare? La prima riga reloadDataaggiorna il, collectionViewquindi attinge di nuovo ai suoi metodi di origine dati ... performBatchUpdatesha un blocco di completamento allegato quindi se entrambi vengono eseguiti sul thread principale, sai che qualsiasi codice inserito /// collection-view finished reloadverrà eseguito con un nuovo e strutturatocollectionView
Magoo

8
Non ha funzionato per me quando collectionView aveva celle di dimensioni dinamiche
ingaham

2
Questo metodo è una soluzione perfetta senza alcun mal di testa.
arjavlad

1
@ingaham Questo funziona perfettamente per me con celle di dimensioni dinamiche, Swift 4.2 IOS 12
Romulo BM

27

In realtà è piuttosto molto semplice.

Quando ad esempio chiami il metodo reloadData di UICollectionView o il metodo invalidateLayout del layout, fai quanto segue:

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

Perché funziona:

Il thread principale (che è dove dovremmo fare tutti gli aggiornamenti dell'interfaccia utente) ospita la coda principale, che è di natura seriale, cioè funziona in modo FIFO. Quindi, nell'esempio precedente, viene chiamato il primo blocco, che ha il nostro reloadDatametodo invocato, seguito da qualsiasi altra cosa nel secondo blocco.

Ora anche il thread principale si sta bloccando. Quindi, se sono reloadDatanecessari 3 secondi per l'esecuzione, l'elaborazione del secondo blocco verrà differita di quei 3 secondi.


2
In realtà l'ho appena testato, il blocco viene chiamato prima di tutti gli altri miei metodi delegati. Quindi non funziona?
taylorcressy

@taylorcressy hey, ho aggiornato il codice, qualcosa che sto usando ora in 2 app di produzione. Inclusa anche la spiegazione.
dezinezync

Non funziona per me. Quando chiamo reloadData, collectionView richiede celle dopo l'esecuzione del secondo blocco. Quindi sto avendo lo stesso problema che sembra avere @taylorcressy.
Raphael

1
Puoi provare a inserire la seconda chiamata di blocco nella prima? Non verificato, ma potrebbe funzionare.
dezinezync

6
reloadDataaccoda ora il caricamento delle celle sul thread principale (anche se chiamato dal thread principale). Quindi le celle non vengono effettivamente caricate ( cellForRowAtIndexPath:non vengono chiamate) dopo i reloadDataritorni. Il wrapping del codice che si desidera eseguire dopo che le celle sono state caricate dispatch_async(dispatch_get_main...e chiamarlo dopo la chiamata a reloadDataotterrà il risultato desiderato.
Tylerc230

12

Solo per aggiungere a un'ottima risposta @dezinezync:

Swift 3+

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

@nikas questo dobbiamo aggiungere in vista è apparso !!
SARATH SASI

1
Non necessariamente, puoi chiamarlo da dove vuoi fare qualcosa quando il layout è finalmente caricato.
Nikans

Stavo ottenendo sfarfallio di scorrimento cercando di far scorrere la mia collectionView alla fine dopo aver inserito gli elementi, questo lo ha risolto, grazie.
Skoua

6

Fai cosi:

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

4

Prova a forzare un passaggio di layout sincrono tramite layoutIfNeeded () subito dopo la chiamata reloadData (). Sembra funzionare sia per UICollectionView che per UITableView su iOS 12.

collectionView.reloadData()
collectionView.layoutIfNeeded() 

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

Grazie uomo! Ho cercato la soluzione di questo problema per molto tempo.
Trzy Gracje

3

Come ha risposto dezinezync , ciò di cui hai bisogno è inviare alla coda principale un blocco di codice dopo reloadDatada un UITableViewor UICollectionView, e quindi questo blocco verrà eseguito dopo l'annullamento dell'accodamento delle celle

Per renderlo più diretto durante l'utilizzo, utilizzerei un'estensione come questa:

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

Può essere implementato anche ad una UITableViewcosì


3

Un approccio diverso utilizzando RxSwift / RxCocoa:

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

1
Mi piace questo. performBatchUpdatesnon funzionava nel mio caso, questo lo fa. Saluti!
Zoltán

@ MichałZiobro, puoi aggiornare il tuo codice da qualche parte? Il metodo dovrebbe essere chiamato solo quando contentSize è stato modificato? Quindi, a meno che non lo modifichi ad ogni scorrimento, dovrebbe funzionare!
Chinh Nguyen

1

Questo funziona per me:

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

2
sciocchezze ... questo non è finito in alcun modo.
Magoo

1

Avevo bisogno di un'azione da fare su tutte le celle visibili quando la vista della raccolta viene caricata prima che sia visibile all'utente, ho usato:

public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if shouldPerformBatch {
        self.collectionView.performBatchUpdates(nil) { completed in
            self.modifyVisibleCells()
        }
    }
}

Fai attenzione che questo verrà chiamato durante lo scorrimento della visualizzazione della raccolta, quindi per evitare questo sovraccarico, ho aggiunto:

private var souldPerformAction: Bool = true

e nell'azione stessa:

private func modifyVisibleCells() {
    if self.shouldPerformAction {
        // perform action
        ...
        ...
    }
    self.shouldPerformAction = false
}

L'azione verrà comunque eseguita più volte, come il numero di celle visibili nello stato iniziale. ma su tutte queste chiamate, avrai lo stesso numero di celle visibili (tutte). E il flag booleano ne impedirà l'esecuzione di nuovo dopo che l'utente ha iniziato a interagire con la vista della raccolta.


1

Ho appena fatto quanto segue per eseguire qualsiasi cosa dopo aver ricaricato la visualizzazione della raccolta. Puoi utilizzare questo codice anche nella risposta API.

self.collectionView.reloadData()

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

1

La soluzione migliore che ho trovato finora è quella di utilizzare CATransactionper gestire il completamento.

Swift 5 :

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

collectionView.reloadData()

CATransaction.commit()

0

Sicuramente fai questo:

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

}

Quindi chiama in questo modo all'interno del tuo VC

let collection = MyCollectionView()

self.collection.reloadData(completion: {

})

Assicurati di utilizzare la sottoclasse !!


0

Questo lavoro per me:


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

Ricarica semplicemente collectionView all'interno degli aggiornamenti batch e poi controlla nel blocco di completamento se è finito o meno con l'aiuto di booleano "finish".

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

0

Di seguito è riportato l'unico approccio che ha funzionato per me.

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

-1

Ecco come ho risolto il problema con Swift 3.0:

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

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

Questo non funzionerà. VisibleCells avrà contenuto non appena viene caricata la prima cella ... il problema è che vogliamo sapere quando è stata caricata l'ultima cella.
Travis M.

-9

Prova questo:

- (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
Non è corretto, cellFor verrà chiamato solo se quella cella sarà visibile, in tal caso l'ultimo elemento visibile non sarà uguale al numero totale di elementi ...
Jirune

-10

Puoi fare così ...

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

    }

Sebbene questo codice possa rispondere alla domanda, fornire un contesto aggiuntivo sul perché e / o come risponde alla domanda ne migliorerebbe significativamente il valore a lungo termine. Si prega di modificare la risposta di aggiungere qualche spiegazione.
Toby Speight
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.