Cum să-i spuneți atunci când UITableView a finalizat ReloadData?

Am încercat pentru a defila la partea de jos a unui UITableView după ce este făcut efectuarea [self.tableView reloadData]

Am avut inițial

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

Dar apoi am citit că reloadData este asincron, astfel încât defilare nu't se întâmple de la sine.tableView,[self.tableView numberOfSections] " și " [self.tableView numberOfRowsinSection` sunt 0.

Multumesc!!!

Ce's ciudat este că eu sunt, folosind:

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

In consola se întoarce Secțiunile = 1, Rand = -1;

Când m-am face exact același NSLogs în cellForRowAtIndexPath am Secțiunile = 1 și rl = 8; (8 este dreapta)

Comentarii la întrebare (4)
Soluția

Reload se întâmplă în următoarea layout trece, ceea ce se întâmplă în mod normal atunci când vă întoarceți de control pentru a rula în buclă (după, să zicem, butonul de acțiune sau ce se întoarce).

Deci, un mod de a rula ceva după masă vedere reîncarcă este pur și simplu pentru a forța tabelul de vedere pentru a efectua aspectul imediat:

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

O altă modalitate este de la programul de după-layout cod pentru a rula mai târziu, folosind 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];
});

UPDATE

După mai multe investigații, am aflat că cei de tabel trimite tableView:numberOfSections: "și" tableView:numberOfRowsInSection:la sursa de date înainte de a reveni la reloadData. Dacă delegatul implementează tableView:heightForRowAtIndexPath:, tabelul de vedere, de asemenea, trimite asta (pentru fiecare rând) înainte de a reveni la reloadData`.

Cu toate acestea, tabelul de vedere nu trimite `tableView:cellForRowAtIndexPath: "sau" tableView:headerViewForSection până la aspect de fază, care se întâmplă în mod implicit atunci când vă întoarceți de control pentru a rula în buclă.

Am găsi, de asemenea, că într-un mic program de testare, codul în întrebarea dumneavoastră în mod corespunzător se va derula la partea de jos a tabelului vedere, fără ca eu sa fac nimic special (cum ar fi trimiterea de layoutIfNeeded sau folosind dispatch_async).

Comentarii (16)

Swift:

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

...somewhere later...

tableView.reloadData {
    println("done")
}

Obiectiv-C:

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

Ca de Xcode 8.2.1, iOS 10, și swift 3,

Puteți determina sfârșitul lui tableView.reloadData() cu ușurință prin utilizarea unui CATransaction bloc:

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

Cele de mai sus, de asemenea, funcționează pentru a determina sfârșitul perioadei de UICollectionView's reloadData() și UIPickerView's reloadAllComponents().

Comentarii (3)

Anii dispatch_async(dispatch_get_main_queue()) metoda de mai sus este nu este garantat de a lucra. Am'm a vedea non-deterministe comportament cu ea, în care, uneori, sistemul a finalizat layoutSubviews și celula de redare înainte de finalizarea bloc, și, uneori, după.

Aici's o soluție care funcționează 100% pentru mine, pe iOS 10. Este nevoie de capacitatea de a instantia UITableView sau UICollectionView ca un obicei subclasă. Aici's UICollectionView soluție, dar's exact la fel pentru UITableView:

CustomCollectionView.h:

#import 

@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

Exemplu de utilizare:

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

A se vedea aici pentru o Rapidă versiune de acest răspuns

Comentarii (7)

Am avut aceleași probleme ca Tyler Sheaffer.

Am implementat solutia în Swift și-a rezolvat problemele mele.

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

Exemplu De Utilizare:

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

Se pare oamenii sunt încă lectură această întrebare și răspunsuri. B/c, am'm editarea răspunsul meu pentru a elimina cuvântul Sincron care este de fapt irelevant pentru acest lucru.

Când [tableView reloadData] întoarce, structurile de date interne spatele tableView au fost actualizate. Prin urmare, atunci când metoda completează în condiții de siguranță puteți defila la partea de jos. Am verificat acest lucru în propria mea aplicație. Acceptate pe scară largă răspunde de @rob-mayoff, în timp ce, de asemenea, confuz în terminologie, recunoaște același în ultima actualizare.

Dacă tableView e't defilare la partea de jos posibil să aveți o problemă în alt cod te-ai't postate. Poate că se schimbă datele după defilare este completă și te're nu reîncărcare și/sau defilare la partea de jos atunci?

Adăuga unele de logare după cum urmează pentru a verifica dacă tabelul de date este corectă după reloadData`. Am codul de mai jos într-o mostră de aplicație și funcționează perfect.

// 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];
Comentarii (11)

Și o UICollectionView versiune, bazată pe kolaworld's răspuns:

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

Are nevoie de testare. Funcționează până în prezent pe iOS 9.2, Xcode 9.2 beta 2, cu o defilare collectionView la un indice, ca o închidere.

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

Utilizare:

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

Eu folosesc acest truc, destul de sigur că am postat deja la un duplicat al întrebarea asta:

-(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;
}
Comentarii (2)

De fapt asta mi-a rezolvat problema:

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

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

Încercați acest mod se va lucra

[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

Eu va executa atunci când masa este complet încărcat

O altă Soluție este de a putea subclasa UITableView

Comentarii (0)

Am încheiat cu o variație de Shawn's soluție:

Creați un obicei UITableView clasa cu un delegat:

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

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

Apoi, în codul meu, eu folosesc

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

De asemenea, asigurați-vă că setați masă vederea CustomTableView în interface builder:

Comentarii (2)

În Swift 3.0 + putem crea o extensie pentru UITableView", cu un " scăpat de Închidere ca mai jos :

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

Și să-l Utilizați ca mai Jos, în cazul în care vreodată doriți :

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

sper că acest lucru va ajuta pentru cineva. noroc!

Comentarii (1)

Detalii

  • Xcode Versiune 10.2.1 (10E1001), Swift 5

Soluție

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

Utilizare

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

Eșantion complet

nu uita sa se adaugă soluție de cod aici

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

Rezultate

Comentarii (0)

Doar să oferim o altă abordare, bazată pe ideea de finalizare fiind 'ultima vizibil' mobil pentru a fi trimis la 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
}

O posibila problema este: Dacă reloadData() a terminat înainte de lastIndexPathToDisplay a fost stabilit, 'ultima vizibil' mobil va fi afișat înainte de lastIndexPathToDisplay a fost stabilit și finalizarea nu va fi numit (și va fi în 'teptare' de stat):

self.mainTable.reloadData()

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

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

Dacă am inversa, ne-am putea trezi cu finalizare fiind declanșat de defilare înainte de reloadData().

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

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

self.mainTable.reloadData()
Comentarii (0)

Încercați acest lucru:

tableView.backgroundColor = .negru

tableView.reloadData()

DispatchQueue.principal.asincron(execută: {

tableView.backgroundColor = .green

})

// De tableView culoare va schimbat de la negru la verde numai după reloadData() funcția completă.

Comentarii (0)

Îl puteți folosi pentru a face ceva după reîncărcare date:

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

Încercați setarea întârzieri:

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