UITableView ReloadData'yı tamamladığında nasıl anlaşılır?


183

Bunu yaptıktan sonra bir UITableView altına kaydırmaya çalışıyorum [self.tableView reloadData]

Aslen sahiptim

 [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];

Ama sonra kaydırma beri olmaz böylece reloadData, asenkron olduğunu okumak self.tableView, [self.tableView numberOfSections]ve [self.tableView numberOfRowsinSectionhepsinin 0 vardır.

Teşekkürler!

Ne garip kullanıyorum:

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

Konsolda Sections = 1, Row = -1;

Ben tam olarak aynı NSLogs cellForRowAtIndexPathyaptığımda Sections = 1 ve Row = 8; (8 haklı)


Bu sorunun olası kopyası: stackoverflow.com/questions/4163579/…
pmk

2
gördüğüm en iyi çözüm. stackoverflow.com/questions/1483581/…
Khaled Annajar

Aşağıdakiler için cevabım size yardımcı olabilir, stackoverflow.com/questions/4163579/…
Suhas Aithal

Yanıtlar:


288

Yeniden yükleme, normalde kontrolü çalıştırma döngüsüne döndüğünüzde gerçekleşen bir sonraki düzen geçişi sırasında gerçekleşir (diyelim ki düğme işleminizden sonra veya ne olursa olsun).

Dolayısıyla, tablo görünümü yeniden yüklendikten sonra bir şey çalıştırmanın bir yolu, tablo görünümünü düzeni hemen gerçekleştirmeye zorlamaktır:

[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];

Başka bir yol, yerleşim sonrası kodunuzu daha sonra aşağıdakileri kullanarak çalışacak şekilde zamanlamaktır 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];
});

GÜNCELLEME

Araştırmamız sonucunda, Tablo görünümü gönderdiğini bulmak tableView:numberOfSections:ve tableView:numberOfRowsInSection:dönmeden önce kendi veri kaynağına reloadData. Temsilci uygularsa tableView:heightForRowAtIndexPath:, tablo görünümü de geri dönmeden önce bunu (her satır için) gönderir reloadData.

Ancak, tablo görünümü göndermez tableView:cellForRowAtIndexPath:veya tableView:headerViewForSectiondenetimi çalışma döngüsüne döndürdüğünüzde varsayılan olarak gerçekleşen düzen aşamasına kadar.

Ayrıca, küçük bir test programında, sorunuzdaki kodun, özel bir şey yapmadan (gönderme layoutIfNeededveya kullanma gibi dispatch_async) tablo görünümünün altına doğru kaydırdığını görüyorum .


3
@rob, tablo veri kaynağınızın ne kadar büyük olduğuna bağlı olarak, aynı çalışma döngüsünde tablo görünümünün altına giderek animasyon oluşturabilirsiniz. Test kodunuzu büyük bir tabloyla denerseniz, bir sonraki çalıştırma döngüsüne kadar kaydırmayı geciktirmek için GCD kullanma hileniz, hemen kaydırma başarısız olur. Ama neyse, bu numara için teşekkürler !!
Bay T

7
Yöntem 2 benim için bilinmeyen bir nedenden dolayı işe yaramadı, ancak bunun yerine ilk yöntemi seçti.
Raj Pawan Gumdal

4
dispatch_async(dispatch_get_main_queue())yöntem çalışma garanti edilmez. Ben bazen sistem layoutSubviews ve tamamlama bloğundan önce ve bazen sonra hücre render tamamladı deterministik olmayan davranış görüyorum. Aşağıda benim için işe yarayan bir cevap göndereceğim.
Tyler Sheaffer

3
dispatch_async(dispatch_get_main_queue())Her zaman çalışmadığına katılıyorum . Burada rastgele sonuçlar görmek.
Ocak'ta Vojto

1
Ana iş parçacığı bir çalışır NSRunLoop. Bir çalışma döngüsünün farklı aşamaları vardır ve belirli bir aşama için bir geri arama zamanlayabilirsiniz (a kullanarak CFRunLoopObserver). UIKit, olay işleyiciniz döndükten sonra düzeni daha sonraki bir aşamada gerçekleşecek şekilde zamanlar.
rob mayoff

106

Swift:

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

...somewhere later...

tableView.reloadData {
    println("done")
}

Objective-C:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];

16
Bu, "yakın gelecekte" ana iş parçacığında bir şey göndermeye eşdeğerdir. Büyük olasılıkla ana iş parçacığının tamamlanma bloğunu ayıklamadan önce tablo görünümünü nesneleri oluşturduğunu görüyorsunuz. İlk etapta bu tür bir hack yapılması tavsiye edilmez, ancak her durumda, bunu deneyecekseniz dispatch_after'ı kullanmalısınız.
seo

1
Rob'un çözümü iyidir, ancak tablo görünümünde satır yoksa işe yaramaz. Aviel'in çözümü, tabloda sadece çizgiler değil, bölümler olsa bile çalışma avantajına sahiptir.
Chrstph SLN

@Christophe Şu anda, Mock görünüm denetleyicimde tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intyöntemi geçersiz kılarak ve yeniden yükleme işleminin tamamlandığını bildirmek istediğim her şeyi geçersiz kılmamı ekleyerek Rob'un güncellemesini tablo görünümünde herhangi bir satır olmadan kullanabildim.
Gobe

49

Xcode 8.2.1, iOS 10 ve swift 3'ten itibaren,

tableView.reloadData()Bir CATransaction bloğunu kullanarak kolayca sonunu belirleyebilirsiniz :

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

Yukarıdakiler ayrıca UICollectionView'ın reloadData () ve UIPickerView'ın reloadAllComponents () 'in sonunu belirlemek için de çalışır.


Also Ayrıca tablo görünümünde, içeride beginUpdatesve endUpdatesçağrılarda satırları el ile ekleme, silme veya taşıma gibi özel yeniden yükleme yapıyorsanız da çalışırım .
Darrarski

Bunun aslında modern bir çözüm olduğuna inanıyorum. Gerçekten de iOS'ta yaygın olan model, örnek ... stackoverflow.com/a/47536770/294884
Fattie

Bunu denedim. Çok garip bir davranışım var. Tablo görünümümde iki headerView var. İçinde setCompletionBlockbenim numberOfSectionsgösterir 2 ... şimdiye kadar çok iyi. Yine de içeride setCompletionBlockyaparsam tableView.headerView(forSection: 1)geri döner nil!!! bu yüzden bu bloğun yeniden yüklenmeden önce gerçekleştiğini veya daha önce bir şey yakaladığını veya yanlış bir şey yaptığımı düşünüyorum. FYI Tyler'ın cevabını denedim ve bu işe yaradı! @Fattie
Honey

32

Yukarıdaki dispatch_async(dispatch_get_main_queue())yöntemin çalışması garanti edilmez . Ben bazen sistem layoutSubviews ve tamamlama bloğundan önce ve bazen sonra hücre render tamamladı deterministik olmayan davranış görüyorum.

İşte iOS 10'da% 100 benim için çalışan bir çözüm. UITableView veya UICollectionView'i özel bir alt sınıf olarak somutlaştırma yeteneği gerektirir. İşte UICollectionView çözümü, ancak UITableView için tamamen aynı:

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

Örnek kullanım:

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

Bu cevabın Swift versiyonu için buraya bakınız


Tek doğru yol budur. Animasyon amacıyla bazı hücrelerin son karelerine ihtiyacım olduğu için projeme ekledim. Swift için de ekledim ve düzenledim. Umursamayı umuyoruz 😉
Jon Vogel

2
Bloğu çağırdıktan sonra, çağrı yapılmasına bağlı olarak, sonraki çağrılar layoutSubviewsolarak ayarlanmalıdır , istenen davranış olmayan güçlü bir referans tutulduğu için bloğun yürütülmesine neden olur. nillayoutSubviewsreloadData
Mark Bourke

bunu neden UITableView için kullanamıyorum? görünür bir arayüz göstermiyor. Başlık dosyasını da içe
aktardım

2
Bu cevaba ek olarak, sadece bir tane varsa, mevcut geri aramayı hızlandırmak mümkün, yani birden fazla arayanın yarış durumu olacak. Çözüm, reloadDataCompletionBlockbir dizi blok yapmak ve yürütme sırasında bunları yinelemek ve bundan sonra diziyi boşaltmaktır.
Tyler Sheaffer

1) Bu Rob'ın ilk cevabı yani layoutIfNeeded'i kullanmakla eşdeğer değil mi? 2) iOS 10'dan neden bahsettiniz, iOS 9'da çalışmıyor ?!
Bal

30

Tyler Sheaffer ile aynı sorunları yaşadım.

Çözümü Swift'te uyguladım ve sorunlarımı çözdü.

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

Hızlı 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()
  }
}

Örnek Kullanım:

tableView.reloadDataWithCompletion() {
 // reloadData is guaranteed to have completed
}

1
Güzel! küçük nit-pick, nil değil iff arayacak if letdiyerek reloadDataCompletionBlock?()kaldırabilirsiniz
Tyler Sheaffer

İos9 benim durumda bu bir ile hiçbir şans
Matjan

self.reloadDataCompletionBlock? { completion() }olmalıydıself.reloadDataCompletionBlock?()
emem

Tablo görünüm yüksekliğinin yeniden boyutlandırılmasını nasıl ele alırım? Daha önce tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates () çağırıyordu
Parth Tamane

10

Ve UICollectionViewkolaworld'ün cevabına dayanan bir versiyon:

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

Test gerekiyor. Şimdiye kadar iOS 9.2, Xcode 9.2 beta 2'de çalışıyor, bir koleksiyon kaydırılıyor Bir kapanış olarak bir dizine görüntüleme.

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

Kullanımı:

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

6

Görünüşe göre millet hala bu soruyu ve cevapları okuyor. B / c, bu gerçekten ilgisiz Synchronous kelimesini kaldırmak için cevabımı düzenliyorum .

When [tableView reloadData]döner, tableView arkasındaki dahili veri yapıları güncellendi. Bu nedenle, yöntem tamamlandığında güvenli bir şekilde alta doğru ilerleyebilirsiniz. Bunu kendi uygulamamda doğruladım. @ Rob-mayoff tarafından yaygın olarak kabul edilen cevap, aynı zamanda terminolojide kafa karıştırırken, son güncellemesinde de aynı şeyi kabul ediyor.

Eğer tableViewaşağıya doğru kaydırmazsanız, göndermediğiniz diğer kodda bir sorun olabilir. Kaydırma tamamlandıktan sonra verileri değiştiriyor olabilirsiniz ve yeniden yüklemiyor ve / veya alta kaydırmıyorsunuz?

Tablo verilerinin daha sonra doğru olduğunu doğrulamak için aşağıdaki gibi bir günlük kaydı ekleyin reloadData. Örnek bir uygulama aşağıdaki kodu var ve mükemmel çalışıyor.

// 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];

Sorularımı güncelledim. NSLogs'umun neden bu şekilde çıkacağını biliyor musunuz?
Alan

8
reloadDatasenkronize değil. Eskiden - şu cevaba bakınız: stackoverflow.com/a/16071589/193896
bendytree

1
Eşzamanlıdır. Bunu test etmek ve örnek bir uygulama ile görmek çok kolay. @ Rob'un bu sorudaki cevabı ile bağlantı kurdunuz. Güncellemesini en alttaki okursanız, bunu da doğruladı. Belki görsel düzen değişikliklerinden bahsediyorsunuz. TableView'in senkronize olarak görsel olarak güncellenmediği ancak verilerin olduğu doğrudur. Bu nedenle OP'nin ihtiyaç duyduğu değerler, iadelerden hemen sonra doğrudur reloadData.
XJones

1
Ne olması gerektiği konusunda kafanız karışabilir reloadData. Test durumumu, görüntülenmezse anlamsız olan b / c çizgisini viewWillAppearkabul etmek için kullanın . Bunun örnekte önbelleğe alınan verileri güncellediğini ve senkronize olduğunu göreceksiniz . Yerleşim düzenlenirken çağrılan diğer temsilci yöntemlerine başvuruyorsanız, görüntülenmezse çağrılamazlar. Senaryoyu yanlış anlıyorsam lütfen açıkla. scrollToRowAtIndexPath:tableViewreloadDatatableViewreloadDatatableViewtableViewtableView
XJones

3
Ne eğlenceli zamanlar. 2014 ve bazı yöntemlerin eşzamanlı ve eşzamansız olup olmadığı konusunda tartışmalar var. Tahmin işi gibi geliyor. Tüm uygulama ayrıntıları bu yöntem adının arkasında tamamen opaktır. Programlama harika değil mi?
fatuhoku

5

Bu hileyi kullanıyorum, zaten bu sorunun bir kopyasına zaten gönderdim:

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

@Fattie, olumlu bir yorum veya olumsuz bir yorum olarak ifade ediyorsanız belirsizdir. Ama "Bu en iyi çözüm gibi görünüyor!" , bu yüzden nispeten konuşursak, bu çözümü en iyi olarak görmüyorsunuz.
Cœur

1
Sahte animasyonun bir yan etkisine mi dayanıyorsunuz? Hiçbir şekilde bu iyi bir fikir değil. Seçici veya GCD yapmayı öğrenin ve doğru şekilde yapın. Btw şimdi tablo yüklü bir yöntem var, sadece prob ince bir özel protokol kullanarak sakıncası yoksa kullanabilirsiniz çünkü kod başka bir yol yerine kodu çağıran çerçeve.
malhal

3

Aslında bu benim sorunumu çözdü:

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

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

2

Bu şekilde deneyin işe yarayacak

[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

Tablo tamamen yüklendiğinde çalışacağım

Diğer Çözüm, UITableView alt sınıf yapabilirsiniz


1

Shawn'un çözümünün bir varyasyonunu kullandım:

Temsilci ile özel bir UITableView sınıfı oluşturun:

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

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

Sonra benim kod, ben kullanın

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

Ayrıca arayüz görünümünüzde tablo görünümünüzü CustomTableView olarak ayarladığınızdan emin olun:

resim açıklamasını buraya girin


Bu işe yarıyor ama sorun, her seferinde tek bir hücre yüklendiğinde vuruluyor, BÜTÜN TABLO GÖRÜNÜMÜ İNDİRMEDİ, bu yüzden açıkça bu soru sorulan soruya göre değil.
Yash Bedi

Doğru, bir kereden fazla çağrılır, ancak her hücrede değil. Böylece, ilk temsilciyi dinleyebilir ve reloadData'yı tekrar çağırana kadar geri kalanını yoksayabilirsiniz.
Sam

1

Swift 3.0'da + biz bir uzantı oluşturabilir UITableViewbir ile escaped Closureaşağıda gibi:

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

Ve istediğiniz gibi Aşağıda kullanın:

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

umarım bu birisine yardım eder. şerefe!


Parlak bir fikir ... sadece bir şey, sadece karışıklığı önlemek için, ben reloadData () yerine t reload fonksiyon adını değiştirdim. Teşekkürler
Vijay Kumar AB

1

ayrıntılar

  • Xcode Sürüm 10.2.1 (10E1001), Swift 5

Çözüm

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

kullanım

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

Tam örnek

Çözüm kodunu buraya eklemeyi unutmayın

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

Sonuçlar

resim açıklamasını buraya girin


0

Sadece tamamlanmanın gönderilecek 'son görünür' hücre olduğu fikrine dayanan başka bir yaklaşım sunmak 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
}

Olası bir sorun şudur: Ayarlanmadan reloadData()önce bitirildiyse lastIndexPathToDisplay, 'son görünür' hücre lastIndexPathToDisplayayarlanmadan önce görüntülenir ve tamamlama çağrılmaz (ve 'bekleme' durumunda olur):

self.mainTable.reloadData()

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

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

Tersine çevirirsek, daha önce kaydırma yaparak tamamlamanın tetiklenmesi ile sonuçlanabiliriz reloadData().

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

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

self.mainTable.reloadData()

0

Bunu dene:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

TableView rengi ancak reloadData()işlev tamamlandıktan sonra siyahtan yeşile dönecektir .


0

Uitableview'nin performBatchUpdates işlevini kullanabilirsiniz

İşte nasıl başarabileceğiniz

self.tableView.performBatchUpdates({

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

        //Reload Completed Use your code here
    }

0

CATransaction'ın yeniden kullanılabilir bir uzantısı oluşturma:

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

Şimdi CATransaction'ın uzantı yöntemini kullanacak bir UITableView uzantısı oluşturuyor:

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

Kullanımı:

tableView.reloadData(completion: {
    //Do the stuff
})

-2

Verileri yeniden yükledikten sonra bir şey yapmak için kullanabilirsiniz:

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

-20

Gecikmeleri ayarlamayı deneyin:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];

14
Bu tehlikeli. Yeniden yüklemenin gecikmesinden daha uzun sürmesi durumunda ne olur?
soymak
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.