Woher weiß ich, dass die UICollectionView vollständig geladen wurde?

97

Ich muss eine Operation ausführen, wenn UICollectionView vollständig geladen wurde, dh zu diesem Zeitpunkt sollten alle Datenquellen- / Layoutmethoden von UICollectionView aufgerufen werden. Woher weiß ich das?? Gibt es eine Delegatenmethode, um den Ladezustand von UICollectionView zu ermitteln?

Jirune
quelle

Antworten:

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];
}
Cullen SUN
quelle
1
Das stürzt für mich ab, wenn ich zurück navigiere, sonst funktioniert es gut.
Ericosg
4
@ericosg hinzufügen [self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];zu -viewWillDisappear:animated:auch.
pxpgraphics
Brillant! Vielen Dank!
Abdo
41
Die Inhaltsgröße ändert sich auch beim Scrollen, sodass sie nicht zuverlässig ist.
Ryan Heitner
Ich habe versucht, eine Sammlungsansicht zu erstellen, die die Daten mithilfe einer asynchronen Restmethode abruft. Dies bedeutet, dass sichtbare Zellen immer leer sind, wenn diese Methode ausgelöst wird, was bedeutet, dass ich zu Beginn zu einem Element in der Sammlung scrolle.
Chucky
155

Das hat bei mir funktioniert:

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

Swift 4-Syntax:

self.collectionView.reloadData()
self.collectionView.performBatchUpdates(nil, completion: {
    (result) in
    // ready
})
myeyesareblind
quelle
1
Arbeitete für mich auf iOS 9
Magoo
5
@ G.Abhisek Errrr, klar .... was musst du erklären? Die erste Zeile reloadDataerfrischt den collectionViewso zieht es auf wieder seine Datenquelle Methoden ... der performBatchUpdateshat einen Block Abschluss so angebracht , dass , wenn beide auf dem Haupt - Thread ausgeführt werden Sie beliebigen Code wissen , dass Put anstelle von /// collection-view finished reloadausführen werde mit einem frischen und angelegtcollectionView
Magoo
8
Funktioniert bei mir nicht, wenn die collectionView Zellen mit dynamischer Größe hatte
ingaham
2
Diese Methode ist die perfekte Lösung ohne Kopfschmerzen.
Arjavlad
1
@ingaham Dies funktioniert perfekt für mich mit Zellen mit dynamischer Größe, Swift 4.2 IOS 12
Romulo BM
27

Es ist eigentlich ziemlich einfach.

Wenn Sie beispielsweise die reloadData-Methode von UICollectionView oder die invalidateLayout-Methode des Layouts aufrufen, gehen Sie wie folgt vor:

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

Warum das funktioniert:

Der Haupt-Thread (in dem wir alle UI-Updates durchführen sollten) enthält die Hauptwarteschlange, die serieller Natur ist, dh auf FIFO-Weise funktioniert. Im obigen Beispiel wird also der erste Block aufgerufen, bei dem unsere reloadDataMethode aufgerufen wird, gefolgt von allem anderen im zweiten Block.

Jetzt blockiert auch der Haupt-Thread. Wenn Sie also reloadData3s für die Ausführung benötigen, wird die Verarbeitung des zweiten Blocks um diese 3s verzögert.

dezinezync
quelle
2
Ich habe das gerade getestet. Der Block wird vor allen meinen anderen Delegatenmethoden aufgerufen. Also funktioniert es nicht?
Taylorcressy
@ Taylorcressy Hey, ich habe den Code aktualisiert, etwas, das ich jetzt in 2 Produktions-Apps verwende. Enthält auch die Erklärung.
dezinezync
Funktioniert bei mir nicht Wenn ich reloadData aufrufe, fordert die collectionView nach der Ausführung des zweiten Blocks Zellen an. Ich habe also das gleiche Problem wie @taylorcressy.
Raphael
1
Können Sie versuchen, den zweiten Blockaufruf in den ersten zu setzen? Nicht überprüft, könnte aber funktionieren.
dezinezync
6
reloadDataJetzt wird das Laden der Zellen in den Hauptthread in die Warteschlange gestellt (auch wenn sie vom Hauptthread aufgerufen werden). Die Zellen werden also cellForRowAtIndexPath:nach der reloadDataRückkehr nicht geladen ( nicht aufgerufen) . Wenn Sie den Code einschließen, der ausgeführt werden soll, nachdem Ihre Zellen geladen wurden, dispatch_async(dispatch_get_main...und diesen nach Ihrem Aufruf aufrufen, reloadDatawird das gewünschte Ergebnis erzielt.
Tylerc230
12

Nur um eine großartige @ dezinezync-Antwort hinzuzufügen:

Swift 3+

collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
    // your stuff here executing after collectionView has been layouted
}
Nikans
quelle
@nikas dies müssen wir hinzufügen in der Ansicht erschien !!
SARATH SASI
1
Nicht unbedingt, Sie können es von jedem Ort aus aufrufen, an dem Sie etwas tun möchten, wenn das Layout endgültig geladen ist.
Nikans
Ich bekam ein Scroll-Flackern, als ich versuchte, meine collectionView am Ende nach dem Einfügen von Elementen zu scrollen. Dies wurde behoben, danke.
Skoua
6

Mach es so:

       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)
        })
Darko
quelle
3

Wie dezinezync antwortete, müssen Sie einen Codeblock reloadDatavon einem UITableViewoder an die Hauptwarteschlange sendenUICollectionView senden, und dieser Block wird dann ausgeführt, nachdem die Zellen aus der Warteschlange entfernt wurden

Um dies bei der Verwendung klarer zu gestalten, würde ich eine Erweiterung wie die folgende verwenden:

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

Es kann auch zu einer implementiert werden UITableViewund

Matheus Rocco
quelle
3

Ein anderer Ansatz mit RxSwift / RxCocoa:

        collectionView.rx.observe(CGSize.self, "contentSize")
            .subscribe(onNext: { size in
                print(size as Any)
            })
            .disposed(by: disposeBag)
Chinh Nguyen
quelle
1
Ich mag das. performBatchUpdateshat in meinem Fall nicht funktioniert, dieser macht es. Prost!
Zoltán
@ MichałZiobro, Kannst du deinen Code irgendwo aktualisieren? Die Methode sollte nur aufgerufen werden, wenn contentSize geändert wurde? Wenn Sie es also nicht bei jeder Schriftrolle ändern, sollte dies funktionieren!
Chinh Nguyen
2

Versuchen Sie, einen synchronen Layoutdurchlauf über layoutIfNeeded () direkt nach dem Aufruf von reloadData () zu erzwingen. Scheint sowohl für UICollectionView als auch für UITableView unter iOS 12 zu funktionieren.

collectionView.reloadData()
collectionView.layoutIfNeeded() 

// cellForItem/sizeForItem calls should be complete
completion?()
Tyler
quelle
Danke, Mann! Ich habe lange nach einer Lösung für dieses Problem gesucht.
Trzy Gracje
1

Das funktioniert bei mir:

__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
    [wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
    [wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];
jAckOdE
quelle
2
Unsinn ... das ist in keiner Weise fertig.
Magoo
1

Ich musste einige Aktionen für alle sichtbaren Zellen ausführen, wenn die Sammlungsansicht geladen wurde, bevor sie für den Benutzer sichtbar ist. Ich habe Folgendes verwendet:

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

Achten Sie darauf, dass dies beim Scrollen durch die Sammlungsansicht aufgerufen wird. Um diesen Overhead zu vermeiden, habe ich Folgendes hinzugefügt:

private var souldPerformAction: Bool = true

und in der Aktion selbst:

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

Die Aktion wird weiterhin mehrmals ausgeführt, wie die Anzahl der sichtbaren Zellen im Ausgangszustand. Bei all diesen Anrufen haben Sie jedoch die gleiche Anzahl sichtbarer Zellen (alle). Das boolesche Flag verhindert, dass es erneut ausgeführt wird, nachdem der Benutzer mit der Sammlungsansicht interagiert hat.

gutte
quelle
1

Ich habe gerade Folgendes getan, um etwas auszuführen, nachdem die Sammlungsansicht neu geladen wurde. Sie können diesen Code auch in API-Antworten verwenden.

self.collectionView.reloadData()

DispatchQueue.main.async {
   // Do Task after collection view is reloaded                
}
Asad Jamil
quelle
0

Auf jeden Fall:

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

}

Rufen Sie dann so in Ihrem VC an

let collection = MyCollectionView()

self.collection.reloadData(completion: {

})

Stellen Sie sicher, dass Sie die Unterklasse verwenden !!

Jon Vogel
quelle
0

Diese Arbeit für mich:


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

}

chrisz
quelle
0

Laden Sie collectionView einfach in Batch-Updates neu und überprüfen Sie dann im Abschlussblock, ob es fertig ist oder nicht, mit Hilfe des booleschen "finish".

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

Die beste Lösung, die ich bisher gefunden habe, ist die Verwendung CATransaction, um die Fertigstellung zu handhaben.

Swift 5 :

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

collectionView.reloadData()

CATransaction.commit()
MaxMedvedev
quelle
-1

So habe ich das Problem mit Swift 3.0 gelöst:

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

    if !self.collectionView.visibleCells.isEmpty {
        // stuff
    }
}
Landonandrey
quelle
Das wird nicht funktionieren. VisibleCells haben Inhalt, sobald die erste Zelle geladen ist. Das Problem ist, dass wir wissen möchten, wann die letzte Zelle geladen wurde.
Travis M.
-9

Versuche dies:

- (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;
}
Rocker
quelle
2
Es ist nicht korrekt, cellFor wird nur aufgerufen, wenn diese Zelle sichtbar sein soll. In diesem Fall entspricht das letzte sichtbare Element nicht der Gesamtzahl der Elemente ...
Jirune
-10

Sie können so tun ...

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

    }
Swapnil
quelle
Obwohl dieser Code die Frage möglicherweise beantwortet, würde die Bereitstellung eines zusätzlichen Kontexts darüber, warum und / oder wie er die Frage beantwortet, ihren langfristigen Wert erheblich verbessern. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen.
Toby Speight