Bagaimana cara mengetahui kapan UITableView telah menyelesaikan ReloadData?

183

Saya mencoba untuk menggulir ke bagian bawah UITableView setelah selesai melakukan [self.tableView reloadData]

Saya awalnya punya

 [self.tableView reloadData]
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];

[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

Tapi kemudian saya membaca bahwa reloadData asynchronous, sehingga scrolling tidak terjadi sejak self.tableView, [self.tableView numberOfSections]dan [self.tableView numberOfRowsinSectionsemuanya 0.

Terima kasih!

Yang aneh adalah saya menggunakan:

[self.tableView reloadData];
NSLog(@"Number of Sections %d", [self.tableView numberOfSections]);
NSLog(@"Number of Rows %d", [self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1);

Di konsol itu mengembalikan Bagian = 1, Baris = -1;

Ketika saya melakukan NSLogs yang sama persis di cellForRowAtIndexPathsaya mendapatkan Bagian = 1 dan Baris = 8; (8 benar)

Alan
sumber
Kemungkinan duplikat dari pertanyaan ini: stackoverflow.com/questions/4163579/…
pmk
2
solusi terbaik yang pernah saya lihat. stackoverflow.com/questions/1483581/…
Khaled Annajar
Jawaban saya untuk yang berikut ini dapat membantu Anda, stackoverflow.com/questions/4163579/…
Suhas Aithal
Coba jawaban saya di sini - stackoverflow.com/questions/4163579/…
Suhas Aithal

Jawaban:

288

Reload terjadi selama pass tata letak berikutnya, yang biasanya terjadi ketika Anda mengembalikan kontrol ke loop dijalankan (setelah, katakanlah, tindakan tombol Anda atau apa pun yang kembali).

Jadi salah satu cara untuk menjalankan sesuatu setelah reload tampilan tabel adalah dengan memaksa tampilan tabel untuk segera melakukan tata letak:

[self.tableView reloadData];
[self.tableView layoutIfNeeded];
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

Cara lain adalah dengan menjadwalkan kode after-layout Anda untuk dijalankan nanti menggunakan dispatch_async:

[self.tableView reloadData];

dispatch_async(dispatch_get_main_queue(), ^{
     NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection:([self.tableView numberOfSections]-1)];

    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});

MEMPERBARUI

Setelah diselidiki lebih lanjut, saya menemukan bahwa tampilan tabel mengirim tableView:numberOfSections:dan tableView:numberOfRowsInSection:ke sumber datanya sebelum kembali dari reloadData. Jika delegasi mengimplementasikan tableView:heightForRowAtIndexPath:, tampilan tabel juga mengirimkan itu (untuk setiap baris) sebelum kembali dari reloadData.

Namun, tampilan tabel tidak mengirim tableView:cellForRowAtIndexPath:atau tableView:headerViewForSectionsampai fase tata letak, yang terjadi secara default ketika Anda mengembalikan kontrol ke loop dijalankan.

Saya juga menemukan bahwa dalam program uji kecil, kode dalam pertanyaan Anda dengan benar menggulir ke bagian bawah tampilan tabel, tanpa saya melakukan sesuatu yang istimewa (seperti mengirim layoutIfNeededatau menggunakan dispatch_async).

rob mayoff
sumber
3
@rob, tergantung pada seberapa besar sumber data tabel Anda, Anda bisa menghidupkan ke bagian bawah tampilan tabel dalam loop yang sama. Jika Anda mencoba kode pengujian dengan tabel besar, trik Anda menggunakan GCD untuk menunda pengguliran hingga putaran proses berikutnya akan berfungsi, sedangkan pengguliran langsung akan gagal. Tapi bagaimanapun, terima kasih untuk trik ini !!
Tn. T
7
Metode 2 tidak berfungsi untuk saya karena alasan yang tidak diketahui, tetapi memilih metode pertama sebagai gantinya.
Raj Pawan Gumdal
4
yang dispatch_async(dispatch_get_main_queue())metode tidak dijamin untuk bekerja. Saya melihat perilaku non-deterministik dengannya, di mana terkadang sistem telah menyelesaikan layoutSubviews dan rendering sel sebelum blok penyelesaian, dan kadang-kadang setelah itu. Saya akan memposting jawaban yang sesuai untuk saya di bawah ini.
Tyler Sheaffer
3
Setuju dengan dispatch_async(dispatch_get_main_queue())tidak selalu berhasil. Melihat hasil acak di sini.
Vojto
1
Utas utama menjalankan sebuah NSRunLoop. Run loop memiliki fase yang berbeda, dan Anda dapat menjadwalkan panggilan balik untuk fase tertentu (menggunakan a CFRunLoopObserver). UIKit menjadwalkan tata letak yang akan terjadi selama fase selanjutnya, setelah event handler Anda kembali.
rob mayoff
106

Cepat:

extension UITableView {
    func reloadData(completion:@escaping ()->()) {
        UIView.animateWithDuration(0, animations: { self.reloadData() })
            { _ in completion() }
    }
}

...somewhere later...

tableView.reloadData {
    println("done")
}

Tujuan-C:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];
Aviel Gross
sumber
16
Itu sama dengan mengirim sesuatu di utas utama dalam "waktu dekat". Kemungkinan Anda hanya melihat tampilan tabel membuat objek sebelum utas utama menyelesaikan blok penyelesaian. Ini tidak disarankan untuk melakukan peretasan seperti ini sejak awal, tetapi bagaimanapun, Anda harus menggunakan dispatch_after jika Anda akan mencoba ini.
seo
1
Solusi Rob baik tetapi tidak berfungsi jika tidak ada baris dalam tampilan tabel. Solusi Aviel memiliki keuntungan untuk bekerja bahkan ketika tabel tidak mengandung garis tetapi hanya bagian.
Chrstph SLN
@Christophe Sampai sekarang, saya dapat menggunakan pembaruan Rob dalam tampilan tabel tanpa baris dengan mengesampingkan tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intmetode pengontrol tampilan Mock saya dan memasukkan dalam menimpa apa pun yang saya ingin beri tahu bahwa pemuatan ulang telah selesai.
Gobe
49

Pada Xcode 8.2.1, iOS 10, dan swift 3,

Anda dapat menentukan akhir tableView.reloadData()dengan mudah menggunakan blok transaksi CAT:

CATransaction.begin()
CATransaction.setCompletionBlock({
    print("reload completed")
    //Your completion code here
})
print("reloading")
tableView.reloadData()
CATransaction.commit()

Di atas juga berfungsi untuk menentukan akhir reloadData UICollectionView () dan reload UIPickerView's AllComponents ().

kolaworld
sumber
👍 Saya juga berfungsi jika Anda melakukan pemuatan ulang kustom, seperti menyisipkan, menghapus, atau memindahkan baris secara manual dalam tampilan tabel, di dalam, beginUpdatesdan endUpdatespanggilan.
Darrarski
Saya percaya ini sebenarnya solusi modern. memang itu adalah pola umum di iOS, contoh ... stackoverflow.com/a/47536770/294884
Fattie
Saya mencoba ini. Saya mendapat perilaku yang sangat aneh. Tampilan tabel saya dengan benar menunjukkan dua headerViews. Di dalam pertunjukan setCompletionBlocksaya numberOfSections2 ... sejauh ini sangat bagus. Namun jika di dalam setCompletionBlockaku melakukannya tableView.headerView(forSection: 1)kembali nil!!! maka saya pikir blok ini terjadi sebelum memuat ulang atau menangkap sesuatu sebelum atau saya melakukan sesuatu yang salah. FYI saya sudah mencoba jawaban Tyler dan itu berhasil! @Fattie
Honey
32

The dispatch_async(dispatch_get_main_queue())Metode di atas tidak dijamin untuk bekerja . Saya melihat perilaku non-deterministik dengannya, di mana kadang-kadang sistem telah menyelesaikan layoutSubviews dan rendering sel sebelum blok penyelesaian, dan kadang-kadang setelah itu.

Inilah solusi yang berfungsi 100% untuk saya, di iOS 10. Ini memerlukan kemampuan untuk membuat Instantiate UITableView atau UICollectionView sebagai subkelas kustom. Inilah solusi UICollectionView, tetapi persis sama untuk UITableView:

CustomCollectionView.h:

#import <UIKit/UIKit.h>

@interface CustomCollectionView: UICollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock;

@end

CustomCollectionView.m:

#import "CustomCollectionView.h"

@interface CustomCollectionView ()

@property (nonatomic, copy) void (^reloadDataCompletionBlock)(void);

@end

@implementation CustomCollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock
{
    self.reloadDataCompletionBlock = completionBlock;
    [self reloadData];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.reloadDataCompletionBlock) {
        self.reloadDataCompletionBlock();
        self.reloadDataCompletionBlock = nil;
    }
}

@end

Contoh penggunaan:

[self.collectionView reloadDataWithCompletion:^{
    // reloadData is guaranteed to have completed
}];

Lihat di sini untuk versi Swift dari jawaban ini

Tyler Sheaffer
sumber
Ini satu-satunya cara yang benar. Menambahkannya ke proyek saya karena saya membutuhkan bingkai akhir dari beberapa sel untuk keperluan animasi. Saya juga menambahkan dan mengedit untuk Swift. Semoga Anda tidak keberatan 😉
Jon Vogel
2
Setelah Anda memanggil blok di layoutSubviewsdalamnya harus ditetapkan nilsebagai panggilan berikutnya untuk layoutSubviews, tidak harus karena reloadDatadipanggil, akan mengakibatkan blok dieksekusi karena ada referensi kuat ditahan, yang bukan perilaku yang diinginkan.
Mark Bourke
mengapa saya tidak bisa menggunakan ini untuk UITableView? itu tidak menunjukkan antarmuka yang terlihat. Saya mengimpor file header juga tetapi masih sama
Julfikar
2
Tambahan untuk jawaban ini adalah bahwa mungkin untuk menolak panggilan balik yang ada jika hanya ada satu, yang berarti beberapa penelepon akan memiliki kondisi ras. Solusinya adalah membuat reloadDataCompletionBlockarray blok dan mengulanginya pada eksekusi dan mengosongkan array setelah itu.
Tyler Sheaffer
1) bukankah ini setara dengan jawaban pertama Rob yaitu menggunakan layoutIfNeeded? 2) mengapa Anda menyebutkan iOS 10, tidak berfungsi di iOS 9 ?!
Sayang
30

Saya memiliki masalah yang sama dengan Tyler Sheaffer.

Saya menerapkan solusinya di Swift dan itu memecahkan masalah saya.

Swift 3.0:

final class UITableViewWithReloadCompletion: UITableView {
  private var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    reloadDataCompletionBlock?()
    reloadDataCompletionBlock = nil
  }


  func reloadDataWithCompletion(completion: @escaping () -> Void) {
    reloadDataCompletionBlock = completion
    self.reloadData()
  }
}

Swift 2:

class UITableViewWithReloadCompletion: UITableView {

  var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    self.reloadDataCompletionBlock?()
    self.reloadDataCompletionBlock = nil
  }

  func reloadDataWithCompletion(completion:() -> Void) {
      reloadDataCompletionBlock = completion
      self.reloadData()
  }
}

Contoh penggunaan:

tableView.reloadDataWithCompletion() {
 // reloadData is guaranteed to have completed
}
Shawn Aukstak
sumber
1
bagus! nit-pick kecil, Anda dapat menghapus if letdengan mengatakan reloadDataCompletionBlock?()yang akan memanggil iff not nil 💥
Tyler Sheaffer
Tidak beruntung dengan ini dalam situasi saya di ios9
Matjan
self.reloadDataCompletionBlock? { completion() }seharusnyaself.reloadDataCompletionBlock?()
emem
Bagaimana cara menangani ukuran ketinggian tampilan tabel? Saya sebelumnya memanggil tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates ()
Parth Tamane
10

Dan sebuah UICollectionViewversi, berdasarkan jawaban kolaworld:

https://stackoverflow.com/a/43162226/1452758

Perlu pengujian. Bekerja sejauh ini di iOS 9.2, Xcode 9.2 beta 2, dengan menggulir collectionView ke indeks, sebagai penutup.

extension UICollectionView
{
    /// Calls reloadsData() on self, and ensures that the given closure is
    /// called after reloadData() has been completed.
    ///
    /// Discussion: reloadData() appears to be asynchronous. i.e. the
    /// reloading actually happens during the next layout pass. So, doing
    /// things like scrolling the collectionView immediately after a
    /// call to reloadData() can cause trouble.
    ///
    /// This method uses CATransaction to schedule the closure.

    func reloadDataThenPerform(_ closure: @escaping (() -> Void))
    {       
        CATransaction.begin()
            CATransaction.setCompletionBlock(closure)
            self.reloadData()
        CATransaction.commit()
    }
}

Pemakaian:

myCollectionView.reloadDataThenPerform {
    myCollectionView.scrollToItem(at: indexPath,
            at: .centeredVertically,
            animated: true)
}
Womble
sumber
6

Tampaknya orang-orang masih membaca pertanyaan ini dan jawabannya. B / c itu, saya mengedit jawaban saya untuk menghapus kata Synchronous yang benar-benar tidak relevan dengan ini.

When [tableView reloadData]kembali, struktur data internal di belakang tableView telah diperbarui. Karena itu, ketika metode ini selesai Anda dapat dengan aman gulir ke bawah. Saya memverifikasi ini di aplikasi saya sendiri. Jawaban yang diterima secara luas oleh @ rob-mayoff, sementara juga membingungkan dalam terminologi, mengakui hal yang sama dalam pembaruan terakhirnya.

Jika Anda tableViewtidak menggulir ke bawah, Anda mungkin memiliki masalah dengan kode lain yang belum Anda posting. Mungkin Anda mengubah data setelah gulir selesai dan Anda tidak memuat ulang dan / atau menggulir ke bawah?

Tambahkan beberapa logging sebagai berikut untuk memverifikasi bahwa data tabel sudah benar setelah reloadData. Saya memiliki kode berikut dalam aplikasi sampel dan berfungsi dengan baik.

// change the data source

NSLog(@"Before reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView reloadData];

NSLog(@"After reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]-1
                                                          inSection:[self.tableView numberOfSections] - 1]
                      atScrollPosition:UITableViewScrollPositionBottom
                              animated:YES];
XJones
sumber
Saya memperbarui pertanyaan saya. Apakah Anda tahu mengapa NSLogs saya akan tampil seperti ini?
Alan
8
reloadDatatidak sinkron. Dulu - lihat jawaban ini: stackoverflow.com/a/16071589/193896
bendytree
1
Ini sinkron. Sangat mudah untuk menguji dan melihat ini dengan aplikasi sampel. Anda tertaut ke jawaban @ rob dalam pertanyaan ini. Jika Anda membaca pembaruannya di bagian bawah, dia juga telah memverifikasi ini. Mungkin Anda berbicara tentang perubahan tata letak visual. Memang benar bahwa tableView tidak diperbarui secara sinkron tetapi datanya. Itu sebabnya nilai-nilai kebutuhan OP benar segera setelah reloadDatakembali.
XJones
1
Anda mungkin bingung tentang apa yang diharapkan terjadi reloadData. Gunakan test case saya viewWillAppearuntuk menerima scrollToRowAtIndexPath:baris b / c yang tidak ada artinya jika tableViewtidak ditampilkan. Anda akan melihat bahwa reloadDatapembaruan data yang di-cache dalam tableViewcontoh dan yang reloadDatasinkron. Jika Anda merujuk pada tableViewmetode delegasi lain yang dipanggil saat tableViewtata letak sedang keluar, mereka tidak akan dipanggil jika tableViewtidak ditampilkan. Jika saya salah memahami skenario Anda, mohon jelaskan.
XJones
3
Waktu yang menyenangkan. Ini 2014, dan ada argumen tentang apakah beberapa metode sinkron dan asinkron atau tidak. Terasa seperti menebak. Semua detail implementasi benar-benar buram di balik nama metode itu. Bukankah pemrograman itu hebat?
fatuhoku
5

Saya menggunakan trik ini, cukup yakin saya sudah mempostingnya ke duplikat dari pertanyaan ini:

-(void)tableViewDidLoadRows:(UITableView *)tableView{
    // do something after loading, e.g. select a cell.
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // trick to detect when table view has finished loading.
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView];
    [self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0];

    // specific to your controller
    return self.objects.count;
}
jahat
sumber
@Fattie tidak jelas apakah Anda bermaksud sebagai komentar positif atau komentar negatif. Tapi saya melihat Anda sudah berkomentar jawaban lain sebagai "ini tampaknya menjadi solusi terbaik!" , jadi saya kira itu relatif, Anda tidak menganggap solusi ini sebagai yang terbaik.
Cœur
1
Mengandalkan efek samping dari animasi palsu? Tidak mungkin itu ide yang bagus. Pelajari perform selector atau GCD dan lakukan dengan benar. Btw sekarang ada metode tabel dimuat Anda hanya bisa menggunakannya jika Anda tidak keberatan menggunakan protokol pribadi yang mungkin baik-baik saja karena itu kerangka kerja yang memanggil kode Anda daripada sebaliknya.
Malhal
3

Sebenarnya yang ini menyelesaikan masalah saya:

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]];
if (visibleSections) {
    // hide the activityIndicator/Loader
}}
Asha Antony
sumber
2

Coba cara ini akan berhasil

[tblViewTerms performSelectorOnMainThread:@selector(dataLoadDoneWithLastTermIndex:) withObject:lastTermIndex waitUntilDone:YES];waitUntilDone:YES];

@interface UITableView (TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex;

@end

@implementation UITableView(TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex
{
    NSLog(@"dataLoadDone");


NSIndexPath* indexPath = [NSIndexPath indexPathForRow: [lastTermIndex integerValue] inSection: 0];

[self selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];

}
@end

Saya akan mengeksekusi ketika tabel sudah dimuat penuh

Solusi lainnya adalah Anda dapat mensubklasifikasikan UITableView

Shashi3456643
sumber
1

Saya akhirnya menggunakan variasi solusi Shawn:

Buat kelas UITableView khusus dengan delegasi:

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

    override func layoutSubviews() {
        super.layoutSubviews()
        self.customDelegate?.CustomTableViewDidLayoutSubviews()
    }
}

Kemudian dalam kode saya, saya gunakan

class SomeClass: UIViewController, CustomTableViewDelegate {

    @IBOutlet weak var myTableView: CustomTableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.myTableView.customDelegate = self
    }

    func CustomTableViewDidLayoutSubviews() {
        print("didlayoutsubviews")
        // DO other cool things here!!
    }
}

Pastikan juga Anda mengatur tampilan tabel Anda ke CustomTableView di pembuat antarmuka:

masukkan deskripsi gambar di sini

Sam
sumber
ini berfungsi tetapi masalahnya adalah metode tersebut dipukul setiap kali dilakukan pemuatan sel tunggal, BUKANLAH tabel seluruh tampilan, jadi jelas jawaban ini tidak sehubungan dengan pertanyaan yang diajukan.
Yash Bedi
Benar, itu dipanggil lebih dari sekali, tetapi tidak di setiap sel. Jadi, Anda bisa mendengarkan delegasi pertama dan mengabaikan sisanya sampai Anda memanggil reloadData lagi.
Sam
1

Di Swift 3.0 + kita dapat membuat ekstensi untuk UITableViewdengan escaped Closureseperti di bawah ini:

extension UITableView {
    func reloadData(completion: @escaping () -> ()) {
        UIView.animate(withDuration: 0, animations: { self.reloadData()})
        {_ in completion() }
    }
}

Dan gunakan seperti di bawah ini di mana pun Anda inginkan:

Your_Table_View.reloadData {
   print("reload done")
 }

Semoga ini bisa membantu seseorang. Bersulang!

Kaldera Chanaka
sumber
Ide cemerlang .. hanya saja, hanya untuk menghindari kebingungan, saya mengubah nama fungsi t reload, daripada reloadData (). Terima kasih
Vijay Kumar AB
1

Detail

  • Xcode Versi 10.2.1 (10E1001), Swift 5

Larutan

import UIKit

// MARK: - UITableView reloading functions

protocol ReloadCompletable: class { func reloadData() }

extension ReloadCompletable {
    func run(transaction closure: (() -> Void)?, completion: (() -> Void)?) {
        guard let closure = closure else { return }
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        closure()
        CATransaction.commit()
    }

    func run(transaction closure: (() -> Void)?, completion: ((Self) -> Void)?) {
        run(transaction: closure) { [weak self] in
            guard let self = self else { return }
            completion?(self)
        }
    }

    func reloadData(completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadData() }, completion: closure)
    }
}

// MARK: - UITableView reloading functions

extension ReloadCompletable where Self: UITableView {
    func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadRows(at: indexPaths, with: animation) }, completion: closure)
    }

    func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections, with: animation) }, completion: closure)
    }
}

// MARK: - UICollectionView reloading functions

extension ReloadCompletable where Self: UICollectionView {

    func reloadSections(_ sections: IndexSet, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections) }, completion: closure)
    }

    func reloadItems(at indexPaths: [IndexPath], completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadItems(at: indexPaths) }, completion: closure)
    }
}

Pemakaian

UITableView

// Activate
extension UITableView: ReloadCompletable { }

// ......
let tableView = UICollectionView()

// reload data
tableView.reloadData { tableView in print(collectionView) }

// or
tableView.reloadRows(at: indexPathsToReload, with: rowAnimation) { tableView in print(tableView) }

// or
tableView.reloadSections(IndexSet(integer: 0), with: rowAnimation) { _tableView in print(tableView) }

UICollectionView

// Activate
extension UICollectionView: ReloadCompletable { }

// ......
let collectionView = UICollectionView()

// reload data
collectionView.reloadData { collectionView in print(collectionView) }

// or
collectionView.reloadItems(at: indexPathsToReload) { collectionView in print(collectionView) }

// or
collectionView.reloadSections(IndexSet(integer: 0)) { collectionView in print(collectionView) }

Sampel lengkap

Jangan lupa untuk menambahkan kode solusi di sini

import UIKit

class ViewController: UIViewController {

    private weak var navigationBar: UINavigationBar?
    private weak var tableView: UITableView?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationItem()
        setupTableView()
    }
}
// MARK: - Activate UITableView reloadData with completion functions

extension UITableView: ReloadCompletable { }

// MARK: - Setup(init) subviews

extension ViewController {

    private func setupTableView() {
        guard let navigationBar = navigationBar else { return }
        let tableView = UITableView()
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.dataSource = self
        self.tableView = tableView
    }

    private func setupNavigationItem() {
        let navigationBar = UINavigationBar()
        view.addSubview(navigationBar)
        self.navigationBar = navigationBar
        navigationBar.translatesAutoresizingMaskIntoConstraints = false
        navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        let navigationItem = UINavigationItem()
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "all", style: .plain, target: self, action: #selector(reloadAllCellsButtonTouchedUpInside(source:)))
        let buttons: [UIBarButtonItem] = [
                                            .init(title: "row", style: .plain, target: self,
                                                  action: #selector(reloadRowButtonTouchedUpInside(source:))),
                                            .init(title: "section", style: .plain, target: self,
                                                  action: #selector(reloadSectionButtonTouchedUpInside(source:)))
                                            ]
        navigationItem.leftBarButtonItems = buttons
        navigationBar.items = [navigationItem]
    }
}

// MARK: - Buttons actions

extension ViewController {

    @objc func reloadAllCellsButtonTouchedUpInside(source: UIBarButtonItem) {
        let elementsName = "Data"
        print("-- Reloading \(elementsName) started")
        tableView?.reloadData { taleView in
            print("-- Reloading \(elementsName) stopped \(taleView)")
        }
    }

    private var randomRowAnimation: UITableView.RowAnimation {
        return UITableView.RowAnimation(rawValue: (0...6).randomElement() ?? 0) ?? UITableView.RowAnimation.automatic
    }

    @objc func reloadRowButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Rows"
        print("-- Reloading \(elementsName) started")
        let indexPathToReload = tableView.indexPathsForVisibleRows?.randomElement() ?? IndexPath(row: 0, section: 0)
        tableView.reloadRows(at: [indexPathToReload], with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }

    @objc func reloadSectionButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Sections"
        print("-- Reloading \(elementsName) started")
        tableView.reloadSections(IndexSet(integer: 0), with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int { return 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = "\(Date())"
        return cell
    }
}

Hasil

masukkan deskripsi gambar di sini

Bodnarchuk dengan mudah
sumber
0

Hanya untuk menawarkan pendekatan lain, berdasarkan gagasan penyelesaian menjadi sel 'terakhir terlihat' untuk dikirim cellForRow.

// Will be set when reload is called
var lastIndexPathToDisplay: IndexPath?

typealias ReloadCompletion = ()->Void

var reloadCompletion: ReloadCompletion?

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // Setup cell

    if indexPath == self.lastIndexPathToDisplay {

        self.lastIndexPathToDisplay = nil

        self.reloadCompletion?()
        self.reloadCompletion = nil
    }

    // Return cell
...

func reloadData(completion: @escaping ReloadCompletion) {

    self.reloadCompletion = completion

    self.mainTable.reloadData()

    self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last
}

Salah satu masalah yang mungkin terjadi adalah: Jika reloadData()telah selesai sebelum lastIndexPathToDisplayditetapkan, sel 'terakhir terlihat' akan ditampilkan sebelum lastIndexPathToDisplaydiatur dan penyelesaian tidak akan dipanggil (dan akan dalam keadaan 'menunggu'):

self.mainTable.reloadData()

// cellForRowAt could be finished here, before setting `lastIndexPathToDisplay`

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

Jika kita mundur, kita bisa berakhir dengan dipicu oleh pengguliran sebelumnya reloadData().

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

// cellForRowAt could trigger the completion by scrolling here since we arm 'lastIndexPathToDisplay' before 'reloadData()'

self.mainTable.reloadData()
bauerMusic
sumber
0

Coba ini:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

Warna tableView akan berubah dari hitam menjadi hijau hanya setelah reloadData()fungsi selesai.

Nirbhay Singh
sumber
0

Anda dapat menggunakan fungsi performBatchUpdates dari uitableview

Inilah cara Anda bisa mencapainya

self.tableView.performBatchUpdates({

      //Perform reload
        self.tableView.reloadData()
    }) { (completed) in

        //Reload Completed Use your code here
    }
Noman Haroon
sumber
0

Membuat ekstensi CATransaction yang dapat digunakan kembali:

public extension CATransaction {
    static func perform(method: () -> Void, completion: @escaping () -> Void) {
        begin()
        setCompletionBlock {
            completion()
        }
        method()
        commit()
    }
}

Sekarang membuat ekstensi UITableView yang akan menggunakan metode ekstensi CATransaction:

public extension UITableView {
    func reloadData(completion: @escaping (() -> Void)) {
       CATransaction.perform(method: {
           reloadData()
       }, completion: completion)
    }
}

Pemakaian:

tableView.reloadData(completion: {
    //Do the stuff
})
Umair
sumber
-2

Anda dapat menggunakannya untuk melakukan sesuatu setelah memuat ulang data:

[UIView animateWithDuration:0 animations:^{
    [self.contentTableView reloadData];
} completion:^(BOOL finished) {
    _isUnderwritingUpdate = NO;
}];
Vit
sumber
-20

Coba atur penundaan:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];
Jake
sumber
14
Ini berbahaya. Bagaimana jika memuat ulang lebih lama dari keterlambatan Anda?
merampok