Jak stwierdzić, kiedy UITableView zakończy ReloadData?


183

Próbuję przewinąć do dołu UITableView po zakończeniu działania [self.tableView reloadData]

Pierwotnie miałem

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

Ale potem przeczytałem, że reloadData jest asynchroniczna, więc przewijanie nie następuje od self.tableView, [self.tableView numberOfSections]i [self.tableView numberOfRowsinSectionwszystkie mają wartość 0.

Dzięki!

Dziwne jest to, że używam:

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

W konsoli zwraca sekcje = 1, wiersz = -1;

Kiedy robię dokładnie te same NSLogs w cellForRowAtIndexPath, otrzymuję sekcje = 1 i wiersz = 8; (8 ma rację)


Możliwe duplikaty tego pytania: stackoverflow.com/questions/4163579/...
pmk

2
najlepsze rozwiązanie, jakie widziałem. stackoverflow.com/questions/1483581/...
Khaled Annajar

Moja odpowiedź na poniższe pytania może ci pomóc, stackoverflow.com/questions/4163579/…
Suhas Aithal

Spróbuj mojej odpowiedzi tutaj - stackoverflow.com/questions/4163579/…
Suhas Aithal

Odpowiedzi:


288

Przeładowanie ma miejsce podczas następnego przejścia do układu, co zwykle ma miejsce, gdy przywrócisz kontrolę do pętli uruchamiania (po, powiedzmy, akcji przycisku lub cokolwiek innego).

Tak więc jednym ze sposobów uruchomienia czegoś po ponownym załadowaniu widoku tabeli jest po prostu wymuszenie, aby widok tabeli natychmiast wykonał układ:

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

Innym sposobem jest zaplanowanie późniejszego uruchomienia kodu po układzie za pomocą 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];
});

AKTUALIZACJA

Po dalszych badaniach stwierdzam, że widok tabeli wysyła tableView:numberOfSections:i tableView:numberOfRowsInSection:do źródła danych przed powrotem zreloadData . Jeśli delegat implementuje tableView:heightForRowAtIndexPath:, widok tabeli również wysyła to (dla każdego wiersza) przed powrotem z reloadData.

Jednak widok tabeli nie jest wysyłany tableView:cellForRowAtIndexPath: ani tableView:headerViewForSectiondo fazy układu, co dzieje się domyślnie po przywróceniu sterowania do pętli uruchamiania.

Zauważyłem również, że w małym programie testowym kod w pytaniu poprawnie przewija się do dolnej części widoku tabeli, a ja nie robię nic specjalnego (jak wysyłanie layoutIfNeededlub używanie dispatch_async).


3
@rob, w zależności od wielkości źródła danych tabeli, możesz animować przechodzenie do dolnej części widoku tabeli w tej samej pętli uruchamiania. Jeśli wypróbujesz kod testowy z ogromną tabelą, sztuczka polegająca na użyciu GCD w celu opóźnienia przewijania do następnej pętli uruchamiania zadziała, a natychmiastowe przewijanie nie powiedzie się. Ale w każdym razie dzięki za tę sztuczkę !!
Pan T

7
Metoda 2 nie działała dla mnie z nieznanego powodu, ale zamiast tego wybrałem pierwszą metodę.
Raj Pawan Gumdal

4
dispatch_async(dispatch_get_main_queue())nie ma gwarancji, że metoda zadziała. Widzę przy tym niedeterministyczne zachowanie, w którym czasami system ukończył podgląd układu i renderowanie komórki przed blokiem ukończenia, a czasem później. Poniżej zamieszczę odpowiedź, która zadziałała dla mnie.
Tyler Sheaffer,

3
Zgadzam się z tym, że dispatch_async(dispatch_get_main_queue())nie zawsze działa. Zobacz losowe wyniki tutaj.
Vojto,

1
Główny wątek prowadzi NSRunLoop. Pętla uruchomieniowa ma różne fazy i można zaplanować oddzwanianie dla określonej fazy (za pomocą a CFRunLoopObserver). UIKit planuje rozmieszczenie układów w późniejszej fazie, po powrocie modułu obsługi zdarzeń.
rob mayoff

106

Szybki:

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

...somewhere later...

tableView.reloadData {
    println("done")
}

Cel C:

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

16
Odpowiada to wysłaniu czegoś w głównym wątku w „najbliższej przyszłości”. Prawdopodobnie widzisz widok tabeli renderującej obiekty, zanim główny wątek usunie blok uzupełniający. Przede wszystkim nie zaleca się takiego hakowania, ale w każdym razie powinieneś użyć polecenia dispatch_after, jeśli chcesz to zrobić.
seo

1
Rozwiązanie Roba jest dobre, ale nie działa, jeśli w widoku tabeli nie ma żadnych wierszy. Rozwiązanie Aviel ma tę zaletę, że działa nawet wtedy, gdy tabela nie zawiera wierszy, a jedynie sekcje.
Chrstph SLN

@Christophe Od tej pory mogłem używać aktualizacji Roba w widoku tabeli bez żadnych wierszy, zastępując tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intmetodę w moim kontrolce widoku próbnego i wstawiając w moim zastąpieniu wszystko, co chciałem powiadomić o zakończeniu przeładowania.
Gobe

49

Począwszy od Xcode 8.2.1, iOS 10 i swift 3,

Możesz łatwo ustalić koniec tableView.reloadData(), używając bloku CATransaction:

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

Powyższe działa również w celu określenia końca reloadData () UICollectionView i reloadAllComponents () UIPickerView.


Works Działa również, jeśli wykonujesz niestandardowe ładowanie, takie jak ręczne wstawianie, usuwanie lub przenoszenie wierszy w widoku tabeli, wewnątrz beginUpdatesi endUpdateswywołaniach.
Darrarski

Wierzę, że to właściwie nowoczesne rozwiązanie. w rzeczywistości jest to powszechny wzór w iOS, na przykład ... stackoverflow.com/a/47536770/294884
Fattie

Próbowałem tego. Mam bardzo dziwne zachowanie. Mój widok tabeli poprawnie pokazuje dwa widoki nagłówka. Wewnątrz setCompletionBlockmoich numberOfSectionspokazów 2 ... jak dotąd tak dobrze. Ale jeśli setCompletionBlockzrobię tableView.headerView(forSection: 1)to w środku , to wraca nil!!! stąd myślę, że ten blok albo dzieje się przed przeładowaniem, albo przechwytuje coś wcześniej, albo robię coś złego. Do Twojej wiadomości spróbowałem odpowiedzi Tylera i zadziałało! @Fattie
Honey

32

Powyższa dispatch_async(dispatch_get_main_queue())metoda nie gwarantuje działania . Widzę przy tym niedeterministyczne zachowanie, w którym czasami system ukończył podgląd układu i renderowanie komórki przed blokiem ukończenia, a czasem później.

Oto rozwiązanie, które działa w 100% dla mnie na iOS 10. Wymaga to możliwości utworzenia instancji UITableView lub UICollectionView jako niestandardowej podklasy. Oto rozwiązanie UICollectionView, ale jest dokładnie takie samo dla 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

Przykładowe użycie:

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

Zobacz tutaj szybką wersję tej odpowiedzi


To jedyny właściwy sposób. Dodałem go do mojego projektu, ponieważ potrzebowałem ostatecznych klatek niektórych komórek do celów animacji. Dodałem również i edytuję Swift. Mam nadzieję, że nie masz nic przeciwko 😉
Jon Vogel

2
Po wywołaniu bloku layoutSubviewsnależy go ustawić niljako, że kolejne wywołania layoutSubviews, niekoniecznie z powodu reloadDatawywołania, spowodują wykonanie bloku, ponieważ utrzymywane jest silne odniesienie, co nie jest pożądanym zachowaniem.
Mark Bourke

dlaczego nie mogę tego użyć w UITableView? nie pokazuje żadnego widocznego interfejsu. Zaimportowałem również plik nagłówka, ale nadal taki sam
Julfikar,

2
Dodatkiem do tej odpowiedzi jest to, że możliwe jest zablokowanie istniejącego wywołania zwrotnego, jeśli jest tylko jeden, co oznacza, że ​​wielu rozmówców będzie miało warunki wyścigowe. Rozwiązaniem jest utworzenie reloadDataCompletionBlocktablicy bloków i iteracja nad nimi po wykonaniu, a następnie opróżnienie tablicy.
Tyler Sheaffer

1) czy nie jest to równoważne z pierwszą odpowiedzią Roba, tj. Z użyciem layoutIfNeeded? 2) dlaczego wspomniałeś o iOS 10, czy to nie działa na iOS 9 ?!
Honey

30

Miałem te same problemy, co Tyler Sheaffer.

Wdrożyłem jego rozwiązanie w Swift i rozwiązało to moje problemy.

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

Przykładowe użycie:

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

1
miły! mały nit-pick, możesz usunąć if let, mówiąc, reloadDataCompletionBlock?()że zadzwoni iff nie zero 💥
Tyler Sheaffer

Nie mam szczęścia z tym w mojej sytuacji na ios9
Matjan

self.reloadDataCompletionBlock? { completion() }powinno byćself.reloadDataCompletionBlock?()
emem

Jak obsłużyć zmianę rozmiaru wysokości widoku tabeli? Wcześniej dzwoniłem tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates ()
Parth Tamane

10

I UICollectionViewwersja oparta na odpowiedzi kolaworld:

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

Wymaga testowania. Działa do tej pory na iOS 9.2, Xcode 9.2 beta 2, z przewijaniem kolekcji View do indeksu, jako zamknięcie.

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

Stosowanie:

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

6

Wygląda na to, że ludzie nadal czytają to pytanie i odpowiedzi. B / c tego, edytuję swoją odpowiedź, aby usunąć słowo Synchronous które jest naprawdę nieistotne dla tego.

When [tableView reloadData]zwraca, wewnętrzne struktury danych za tableView zostały zaktualizowane. Dlatego po zakończeniu metody możesz bezpiecznie przewinąć na dół. Zweryfikowałem to we własnej aplikacji. Powszechnie akceptowana odpowiedź @ rob-mayoff, choć myląca terminologicznie, potwierdza to samo w swojej ostatniej aktualizacji.

Jeśli tableViewnie przewijasz do dołu, możesz mieć problem z innym kodem, który nie został opublikowany. Być może zmieniasz dane po zakończeniu przewijania i nie ładujesz ponownie i / lub przewijasz do dołu?

Dodaj rejestrowanie w następujący sposób, aby sprawdzić, czy dane tabeli są poprawne po reloadData. Mam następujący kod w przykładowej aplikacji i działa idealnie.

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

Zaktualizowałem swoje pytania. Czy wiesz, dlaczego moje NSLogs miałyby tak wyświetlać?
Alan

8
reloadDatanie jest synchroniczny. Kiedyś - patrz ta odpowiedź: stackoverflow.com/a/16071589/193896
bendytree

1
Jest synchroniczny. Bardzo łatwo przetestować i zobaczyć to za pomocą przykładowej aplikacji. Link do odpowiedzi @ rob w tym pytaniu. Jeśli przeczytasz jego aktualizację na dole, on również to zweryfikował. Być może mówisz o zmianach układu wizualnego. To prawda, że ​​tableView nie jest widocznie aktualizowany synchronicznie, ale dane są. Dlatego wartości, których potrzebuje OP, są poprawne natychmiast po reloadDatapowrocie.
XJones

1
Możesz być zdezorientowany co do tego, co się wydarzy reloadData. Użyj mojego przypadku testowego, viewWillAppearaby zaakceptować scrollToRowAtIndexPath:linię b / c, która nie ma znaczenia, jeśli tableViewnie jest wyświetlana. Zobaczysz, że reloadDatazaktualizował dane buforowane w tableViewinstancji i reloadDatajest synchroniczny. Jeśli odwołujesz się do innych tableViewmetod delegowania wywoływanych podczas tableViewukładania, nie zostaną one wywołane, jeśli tableViewnie zostanie wyświetlone. Jeśli nie rozumiem twojego scenariusza, proszę wyjaśnij.
XJones

3
Co za zabawne czasy. Jest rok 2014 i istnieją argumenty, czy jakaś metoda jest synchroniczna i asynchroniczna, czy nie. Wygląda na zgadywanie. Wszystkie szczegóły implementacji są całkowicie nieprzejrzyste za tą nazwą metody. Czy programowanie nie jest świetne?
fatuhoku

5

Używam tej sztuczki, jestem prawie pewien, że opublikowałem ją już w duplikacie tego pytania:

-(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 nie jest jasne, czy masz na myśli komentarz pozytywny czy negatywny. Ale widziałem, że skomentowałeś inną odpowiedź jako „wydaje się to najlepszym rozwiązaniem!” , więc myślę, że relatywnie rzecz biorąc, nie uważasz tego rozwiązania za najlepsze.
Cœur

1
Czy polegasz na fałszywej animacji? Nie ma mowy, żeby to był dobry pomysł. Naucz się wykonywać selektor lub GCD i robić to poprawnie. Poza tym istnieje teraz metoda załadowana do tabeli, której możesz po prostu użyć, jeśli nie masz nic przeciwko użyciu prywatnego protokołu, który jest w porządku, ponieważ jest to struktura wywołująca twój kod, a nie odwrotnie.
malhal

3

Właściwie to rozwiązało mój problem:

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

Wypróbuj w ten sposób to zadziała

[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

Wykonam, gdy tabela zostanie całkowicie załadowana

Innym rozwiązaniem jest podklasa UITableView


1

Skończyło się na odmianie Shawna:

Utwórz niestandardową klasę UITableView z delegatem:

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

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

Następnie w moim kodzie używam

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

Upewnij się również, że ustawiłeś widok tabeli na CustomTableView w kreatorze interfejsów:

wprowadź opis zdjęcia tutaj


to działa, ale problem polega na tym, że metoda zostaje trafiona za każdym razem, gdy ładuje się pojedynczą komórkę, NIE WIDOK CAŁEGO STÓŁU PRZEKAZUJ, więc wyraźnie ta odpowiedź nie dotyczy zadanego pytania.
Yash Bedi

To prawda, że ​​jest wywoływany więcej niż raz, ale nie w każdej komórce. Możesz więc wysłuchać pierwszego delegata i zignorować resztę, aż ponownie wywołasz reloadData.
Sam

1

W Swift 3.0 + możemy stworzyć rozszerzenie do UITableViewz escaped Closurejak poniżej:

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

I używaj go jak poniżej, gdziekolwiek chcesz:

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

mam nadzieję, że to komuś pomoże. Twoje zdrowie!


Genialny pomysł ... jedyne, żeby uniknąć zamieszania, zmieniłem nazwę funkcji t reload, zamiast reloadData (). Dzięki
Vijay Kumar AB

1

Detale

  • Wersja Xcode 10.2.1 (10E1001), Swift 5

Rozwiązanie

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

Stosowanie

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

Pełna próbka

Nie zapomnij dodać tutaj kodu rozwiązania

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

Wyniki

wprowadź opis zdjęcia tutaj


0

Aby zaoferować inne podejście, oparte na założeniu, że ukończenie jest „ostatnią widoczną” komórką, do której należy wysłać 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
}

Jednym z możliwych problemów jest: Jeśli reloadData()zakończyło się przed lastIndexPathToDisplayustawieniem, komórka „ostatnia widoczna” zostanie wyświetlona przed lastIndexPathToDisplayustawieniem, a zakończenie nie zostanie wywołane (i będzie w stanie „oczekiwania”):

self.mainTable.reloadData()

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

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

Jeśli cofniemy, możemy zakończyć się wyzwalaniem przez przewijanie wcześniej 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

Spróbuj tego:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

Kolor tableView zmieni się z czarnego na zielony dopiero po zakończeniu reloadData()funkcji.


0

Możesz użyć funkcji performBatchUpdates programu uitableview

Oto jak możesz to osiągnąć

self.tableView.performBatchUpdates({

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

        //Reload Completed Use your code here
    }

0

Tworzenie rozszerzenia CATransaction wielokrotnego użytku:

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

Teraz tworzymy rozszerzenie UITableView, które wykorzystywałoby metodę rozszerzenia CATransaction:

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

Stosowanie:

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

-2

Możesz go użyć do zrobienia czegoś po ponownym załadowaniu danych:

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

-20

Spróbuj ustawić opóźnienia:

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

14
To jest niebezpieczne. Co jeśli przeładowanie trwa dłużej niż opóźnienie?
okr.
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.