Skąd mam wiedzieć, że UICollectionView został całkowicie załadowany?


98

Muszę wykonać jakąś operację za każdym razem, gdy UICollectionView został całkowicie załadowany, tj. W tym czasie należy wywołać wszystkie metody źródła danych / układu UICollectionView. Skąd mam to wiedzieć? Czy istnieje metoda delegata, aby poznać stan załadowania UICollectionView?

Odpowiedzi:


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
to się zawiesza, kiedy wracam, w przeciwnym razie działa dobrze.
— ericosg

4
@ericosg również dodaj [self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];do -viewWillDisappear:animated:.
— pxpgraphics

Znakomity! Dziękuję Ci bardzo!
— Abdo

41
Rozmiar treści zmienia się również podczas przewijania, więc nie jest wiarygodny.
— Ryan Heitner

Próbowałem uzyskać widok kolekcji, który pobiera dane przy użyciu metody odpoczynku asynchronicznego. Oznacza to, że widoczne komórki są zawsze puste, gdy ta metoda jest uruchamiana, co oznacza, że ​​na początku przewijam do elementu w kolekcji.
— Chucky,

155

To zadziałało dla mnie:

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

Składnia Swift 4:

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

1
Pracował dla mnie na iOS 9
— Magoo

5
@ G.Abhisek Errrr, jasne… co potrzebujesz wyjaśnienia? Pierwsza linia reloadDataodświeża, collectionViewwięc ponownie rysuje na swoich metodach źródła danych ... performBatchUpdatesma dołączony blok uzupełniania, więc jeśli oba są wykonywane w głównym wątku, znasz dowolny kod, który wstawiony w miejsce /// collection-view finished reloadwill, zostanie wykonany z nowym i ułożonymcollectionView
— Magoo

8
Nie działa dla mnie, gdy collectionView miała komórki o dynamicznych rozmiarach
— ingaham

2
Ta metoda jest idealnym rozwiązaniem bez bólu głowy.
— arjavlad

1
@ingaham Działa to idealnie w przypadku komórek o dynamicznych rozmiarach, Swift 4.2 IOS 12
— Romulo BM

27

W rzeczywistości jest to raczej bardzo proste.

Gdy na przykład wywołujesz metodę reloadData UICollectionView lub metodę invalidateLayout układu, wykonaj następujące czynności:

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

Dlaczego to działa:

Główny wątek (w którym powinniśmy robić wszystkie aktualizacje interfejsu użytkownika) zawiera kolejkę główną, która ma charakter seryjny, czyli działa w stylu FIFO. W powyższym przykładzie zostaje wywołany pierwszy blok, który wywołuje naszą reloadDatametodę, a następnie cokolwiek innego w drugim bloku.

Teraz główny wątek również się blokuje. Więc jeśli reloadDatawykonanie zajmuje 3 sekundy, przetwarzanie drugiego bloku zostanie odroczone o te 3 sekundy.


2
Właśnie to przetestowałem. Blok jest wywoływany przed wszystkimi innymi metodami delegata. Więc to nie działa?
— taylorcressy

@taylorcressy hej, zaktualizowałem kod, coś, czego używam teraz w 2 aplikacjach produkcyjnych. Zawiera również wyjaśnienie.
— dezinezync

Nie działa na mnie. Kiedy wywołuję reloadData, collectionView żąda komórek po wykonaniu drugiego bloku. Więc mam ten sam problem, który wydaje się mieć @taylorcressy.
— Raphael

1
Czy możesz spróbować umieścić drugą blokadę w pierwszej? Niezweryfikowane, ale może działać.
— dezinezync

6
reloadDatateraz kolejkuje ładowanie komórek w głównym wątku (nawet jeśli są wywoływane z głównego wątku). Więc komórki nie są faktycznie ładowane ( cellForRowAtIndexPath:nie są wywoływane) po reloadDatapowrocie. Zawijanie kodu, który chcesz wykonać po załadowaniu komórek, dispatch_async(dispatch_get_main...i wywołanie go po wywołaniu reloadDataprzyniesie pożądany rezultat.
— Tylerc230

12

Aby dodać do świetnej odpowiedzi @dezinezync:

Swift 3+

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

@nikas to, które musimy dodać, pojawiło się !!
— SARATH SASI

1
Niekoniecznie, możesz to wywołać z dowolnego miejsca, w którym chcesz coś zrobić, gdy układ zostanie ostatecznie załadowany.
— nikans

Otrzymywałem migotanie przewijania, próbując przewinąć mój widok kolekcji na końcu po wstawieniu elementów, to rozwiązało problem, dzięki.
— Skoua

6

Zrób to tak:

       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

Spróbuj wymusić synchroniczne przekazywanie układu za pomocą layoutIfNeeded () zaraz po wywołaniu reloadData (). Wydaje się, że działa zarówno dla UICollectionView, jak i UITableView w systemie iOS 12.

collectionView.reloadData()
collectionView.layoutIfNeeded() 

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

Dzięki! Dość długo szukałem rozwiązania tego problemu.
— Trzy Gracje

3

Jak odpowiedział dezinezync , wystarczy wysłać do kolejki głównej blok kodu po reloadDataz UITableViewlub UICollectionViewi wtedy ten blok zostanie wykonany po dekolejkowaniu komórek

Aby było to prostsze podczas używania, użyłbym takiego rozszerzenia:

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

Może być również realizowane Do UITableViewtakże


3

Inne podejście przy użyciu RxSwift / RxCocoa:

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

1
Podoba mi się to. performBatchUpdatesnie działał w moim przypadku, ten to robi. Twoje zdrowie!
— Zoltán

@ MichałZiobro, Czy możesz gdzieś zaktualizować swój kod? Metodę należy wywołać tylko wtedy, gdy zmieniono contentSize? Więc jeśli nie zmienisz tego na każdym zwoju, to powinno działać!
— Chinh Nguyen

1

To działa dla mnie:

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

2
nonsens ... to w żaden sposób się nie skończyło.
— Magoo

1

Musiałem wykonać jakąś akcję na wszystkich widocznych komórkach, gdy widok kolekcji zostanie załadowany, zanim będzie widoczny dla użytkownika, użyłem:

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

Zwróć uwagę, że zostanie to wywołane podczas przewijania widoku kolekcji, więc aby temu zapobiec, dodałem:

private var souldPerformAction: Bool = true

aw samej akcji:

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

Akcja będzie nadal wykonywana wiele razy, ponieważ liczba widocznych komórek w stanie początkowym. ale we wszystkich tych wywołaniach będziesz mieć taką samą liczbę widocznych komórek (wszystkie). A flaga logiczna uniemożliwi ponowne uruchomienie po rozpoczęciu interakcji użytkownika z widokiem kolekcji.


1

Wykonałem następujące czynności, aby wykonać cokolwiek po ponownym załadowaniu widoku kolekcji. Możesz użyć tego kodu nawet w odpowiedzi API.

self.collectionView.reloadData()

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

1

Najlepszym rozwiązaniem, jakie do tej pory znalazłem, jest CATransactionobsługa kompletacji.

Swift 5 :

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

collectionView.reloadData()

CATransaction.commit()

0

Def zrób to:

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

}

Następnie zadzwoń w ten sposób w swoim VC

let collection = MyCollectionView()

self.collection.reloadData(completion: {

})

Upewnij się, że używasz podklasy !!


0

Ta praca dla mnie:


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

Po prostu przeładuj kolekcję Wyświetl aktualizacje wsadowe, a następnie sprawdź w bloku uzupełniania, czy zostało to zakończone, czy nie, za pomocą boolowskiego „finish”.

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

0

Poniżej znajduje się jedyne podejście, które zadziałało dla mnie.

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

-1

Oto jak rozwiązałem problem ze Swift 3.0:

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

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

To nie zadziała. VisibleCells będą miały zawartość zaraz po załadowaniu pierwszej komórki ... Problem polega na tym, że chcemy wiedzieć, kiedy załadowano ostatnią komórkę.
— Travis M.

-9

Spróbuj tego:

- (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
To nie jest poprawne, funkcja cellFor zostanie wywołana tylko wtedy, gdy ta komórka będzie widoczna, w takim przypadku ostatni widoczny element nie będzie równy całkowitej liczbie elementów ...
— Jirune

-10

Możesz to zrobić ...

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

    }

Chociaż ten kod może odpowiedzieć na pytanie, dostarczenie dodatkowego kontekstu dotyczącego tego, dlaczego i / lub w jaki sposób odpowiada na pytanie, znacznie poprawiłoby jego długoterminową wartość. Proszę edytować swoje odpowiedzi, aby dodać trochę wyjaśnień.
— Toby Speight
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.