Bagaimana saya tahu bahwa UICollectionView telah dimuat sepenuhnya?

98

Saya harus melakukan beberapa operasi setiap kali UICollectionView telah dimuat sepenuhnya, yaitu pada saat itu semua metode sumber data / tata letak UICollectionView harus dipanggil. Bagaimana saya tahu itu ?? Apakah ada metode delegasi untuk mengetahui status UICollectionView dimuat?

Jirune
sumber

Jawaban:

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
sumber
1
ini macet untuk saya ketika saya menavigasi kembali, jika tidak berfungsi dengan baik.
ericosg
4
@ericosg menambahkan [self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];untuk -viewWillDisappear:animated:juga.
pxpgraphics
Cemerlang! Terima kasih banyak!
Abdo
41
Ukuran konten juga berubah saat di-scroll sehingga tidak dapat diandalkan.
Ryan Heitner
Saya telah mencoba untuk tampilan koleksi yang mendapatkan datanya menggunakan metode istirahat async. Ini berarti Sel Terlihat selalu kosong saat metode ini diaktifkan, artinya saya menggulir ke item dalam koleksi di awal.
Chucky
155

Ini berhasil untuk saya:

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

Sintaks Swift 4:

self.collectionView.reloadData()
self.collectionView.performBatchUpdates(nil, completion: {
    (result) in
    // ready
})
myeyesareblind
sumber
1
Bekerja untuk saya di iOS 9
Magoo
5
@ G.Abhisek Errrr, tentu .... apa yang perlu Anda jelaskan? Baris pertama reloadDatamemuat ulang collectionViewsehingga menggunakan metode sumber datanya lagi ... yang performBatchUpdatesmemiliki blok penyelesaian terpasang sehingga jika keduanya dilakukan pada utas utama, Anda tahu kode apa pun yang menggantikan /// collection-view finished reloadakan dieksekusi dengan segar dan ditatacollectionView
Magoo
8
Tidak bekerja untuk saya saat collectionView memiliki sel berukuran dinamis
ingaham
2
Metode ini adalah solusi sempurna tanpa sakit kepala.
arjavlad
1
@ingaham Ini bekerja dengan sempurna untuk saya dengan sel berukuran dinamis, Swift 4.2 IOS 12
Romulo BM
27

Ini sebenarnya sangat sederhana.

Saat Anda misalnya memanggil metode reloadData UICollectionView atau metode invalidateLayout layout, Anda melakukan hal berikut:

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

Mengapa ini berhasil:

Utas utama (di mana kita harus melakukan semua pembaruan UI) menampung antrian utama, yang bersifat serial, yaitu bekerja dengan cara FIFO. Jadi dalam contoh di atas, blok pertama dipanggil, yang mana reloadDatametode kita sedang dipanggil, diikuti oleh apa pun di blok kedua.

Sekarang utas utama juga memblokir. Jadi jika Anda reloadDatamembutuhkan 3 detik untuk dieksekusi, pemrosesan blok kedua akan ditangguhkan oleh 3 detik tersebut.

dezinezync.dll
sumber
2
Saya sebenarnya baru saja menguji ini, Blok dipanggil sebelum semua metode delegasi saya yang lain. Jadi tidak berhasil?
taylorcressy
@taylorcressy hei, saya telah memperbarui kode, sesuatu yang saya gunakan sekarang di 2 aplikasi produksi. Juga disertakan penjelasannya.
dezinezync
Tidak berhasil untuk saya. Saat saya memanggil reloadData, collectionView meminta sel setelah eksekusi blok kedua. Jadi saya mengalami masalah yang sama seperti yang dialami @taylorcressy.
Raphael
1
Bisakah Anda mencoba melakukan panggilan blokir kedua dalam yang pertama? Belum diverifikasi, tetapi mungkin berhasil.
dezinezync
6
reloadDatasekarang mengantrekan pemuatan sel di thread utama (bahkan jika dipanggil dari thread utama). Jadi sel tidak benar-benar dimuat ( cellForRowAtIndexPath:tidak dipanggil) setelah reloadDatadikembalikan. Kode pembungkus yang ingin Anda jalankan setelah sel Anda dimuat dispatch_async(dispatch_get_main...dan memanggilnya setelah panggilan Anda ke reloadDataakan mencapai hasil yang diinginkan.
Tylerc230
12

Hanya untuk menambah jawaban @dezinezync yang bagus:

Cepat 3+

collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
    // your stuff here executing after collectionView has been layouted
}
nikans
sumber
@nikas ini kita harus menambahkan tampilan memang muncul !!
SARATH SASI
1
Belum tentu, Anda dapat memanggilnya dari mana pun Anda ingin melakukan sesuatu saat tata letak akhirnya dimuat.
nikans
Saya mendapatkan film gulir yang mencoba membuat gulungan collectionView saya di akhir setelah memasukkan item, ini memperbaikinya, terima kasih.
Skoua
6

Lakukan seperti ini:

       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
sumber
4

Coba paksa penerusan tata letak sinkron melalui layoutIfNeeded () tepat setelah panggilan reloadData (). Tampaknya berfungsi untuk UICollectionView dan UITableView di iOS 12.

collectionView.reloadData()
collectionView.layoutIfNeeded() 

// cellForItem/sizeForItem calls should be complete
completion?()
Tyler
sumber
Terima kasih sobat! Saya sudah mencari solusi dari masalah itu sejak lama.
Trzy Gracje
3

Saat dezinezync menjawab, yang Anda butuhkan adalah mengirimkan blok kode setelah reloadDatadari a UITableViewatau ke antrean utama UICollectionView, dan kemudian blok ini akan dijalankan setelah sel dequeuing

Untuk membuatnya lebih lurus saat menggunakan, saya akan menggunakan ekstensi seperti ini:

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

Ini juga dapat diterapkan ke a UITableViewjuga

Matheus Rocco
sumber
3

Pendekatan yang berbeda menggunakan RxSwift / RxCocoa:

        collectionView.rx.observe(CGSize.self, "contentSize")
            .subscribe(onNext: { size in
                print(size as Any)
            })
            .disposed(by: disposeBag)
Chinh Nguyen
sumber
1
Saya menyukai ini. performBatchUpdatestidak berfungsi dalam kasus saya, yang ini berhasil. Bersulang!
Zoltán
@ MichałZiobro, Dapatkah Anda memperbarui kode Anda di suatu tempat? Metode ini harus dipanggil hanya jika contentSize diubah? Jadi, kecuali Anda mengubahnya di setiap gulungan, ini seharusnya berfungsi!
Chinh Nguyen
1

Ini bekerja untuk saya:

__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
    [wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
    [wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];
jAckOdE
sumber
2
omong kosong ... ini belum selesai dengan cara apa pun.
Magoo
1

Saya memerlukan beberapa tindakan yang harus dilakukan pada semua sel yang terlihat saat tampilan koleksi dimuat sebelum terlihat oleh pengguna, saya menggunakan:

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

Perhatikan bahwa ini akan dipanggil saat menggulir melalui tampilan koleksi, jadi untuk mencegah overhead ini, saya menambahkan:

private var souldPerformAction: Bool = true

dan dalam aksinya itu sendiri:

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

Tindakan tersebut masih akan dilakukan beberapa kali, sebagai jumlah sel yang terlihat pada keadaan awal. tetapi pada semua panggilan itu, Anda akan memiliki jumlah sel terlihat yang sama (semuanya). Dan bendera boolean akan mencegahnya berjalan kembali setelah pengguna mulai berinteraksi dengan tampilan koleksi.

usus
sumber
1

Saya baru saja melakukan hal berikut untuk melakukan apa pun setelah tampilan koleksi dimuat ulang. Anda dapat menggunakan kode ini bahkan dalam respons API.

self.collectionView.reloadData()

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

Solusi terbaik yang saya temukan sejauh ini adalah menggunakan CATransactionuntuk menangani penyelesaian.

Cepat 5 :

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

collectionView.reloadData()

CATransaction.commit()
MaxMedvedev
sumber
0

Def lakukan ini:

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

}

Kemudian panggil seperti ini di dalam VC Anda

let collection = MyCollectionView()

self.collection.reloadData(completion: {

})

Pastikan Anda menggunakan subclass !!

Jon Vogel
sumber
0

Ini bekerja untuk saya:


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

Cukup muat ulang collectionView di dalam pembaruan batch dan kemudian periksa di blok penyelesaian apakah sudah selesai atau belum dengan bantuan boolean "finish".

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

Di bawah ini adalah satu-satunya pendekatan yang berhasil bagi saya.

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

Beginilah cara saya memecahkan masalah dengan Swift 3.0:

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

    if !self.collectionView.visibleCells.isEmpty {
        // stuff
    }
}
landonandrey
sumber
Ini tidak akan berhasil. VisibleCells akan memiliki konten segera setelah sel pertama dimuat ... masalahnya adalah kita ingin tahu kapan sel terakhir telah dimuat.
Travis M.
-9

Coba ini:

- (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;
}
Kursi goyang
sumber
2
Ini tidak benar, cellFor akan dipanggil hanya jika sel itu akan terlihat, dalam hal itu item terakhir yang terlihat tidak akan sama dengan jumlah total item ...
Jirune
-10

Anda bisa melakukan seperti ini ...

  - (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
sumber
Meskipun kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang mengapa dan / atau bagaimana kode ini menjawab pertanyaan akan secara signifikan meningkatkan nilai jangka panjangnya. Harap edit jawaban Anda untuk menambahkan penjelasan.
Toby Speight