UITableViewがReloadDataを完了したことを確認する方法は?


183

実行が完了した後、UITableViewの一番下までスクロールしようとしています [self.tableView reloadData]

私はもともと持っていました

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

しかし、私はreloadDataが非同期であるため、スクロールは以来起こらないことを読んでself.tableView[self.tableView numberOfSections]そして[self.tableView numberOfRowsinSectionすべて0です。

ありがとう!

奇妙なのは私が使っていることです:

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

コンソールでは、セクション= 1、行= -1を返します。

でまったく同じNSLogを実行するcellForRowAtIndexPathと、Sections = 1およびRow = 8になります。(8が正しい)


この質問の可能性のある重複:stackoverflow.com/questions/4163579/...
PMK

2
私が見た中で最高の解決策。stackoverflow.com/questions/1483581/...
ハレドAnnajar

以下に対する私の答えが役立つかもしれません、stackoverflow.com
questions / 4163579 /…

ここに私の答えをお試しください- stackoverflow.com/questions/4163579/...
Suhas Aithal

回答:


288

リロードは次のレイアウトパス中に発生します。これは通常、制御を実行ループに戻したときに発生します(たとえば、ボタンアクションなどが戻った後)。

したがって、テーブルビューのリロード後に何かを実行する1つの方法は、テーブルビューにレイアウトをすぐに強制的に実行させることです。

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

別の方法は、後で使用するようにレイアウト後のコードをスケジュールすること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];
});

更新

さらに調査すると、私はテーブルビューを送信することを発見tableView:numberOfSections:してtableView:numberOfRowsInSection:から復帰する前に、そのデータ・ソースにreloadData。デリゲートがを実装しているtableView:heightForRowAtIndexPath:場合、テーブルビューはから戻る前に(行ごとに)も送信しreloadDataます。

ただし、テーブルビューは送信されませんtableView:cellForRowAtIndexPath:か、tableView:headerViewForSectionあなたが実行ループに制御を返す時にデフォルトで起こるレイアウト相、まで。

また、小さなテストプログラムでは、質問のコードがテーブルビューの下部まで適切にスクロールし、特別な処理(送信や使用など)を行わなくても確認できます。layoutIfNeededdispatch_async


3
@rob、テーブルデータソースのサイズに応じて、同じ実行ループでテーブルビューの下部に移動するアニメーションを作成できます。巨大なテーブルを使用してテストコードを試す場合、GCDを使用して次の実行ループが機能するまでスクロールを遅らせるトリックは、すぐにスクロールに失敗します。とにかく、このトリックに感謝します!!
T氏

7
方法2は、なんらかの理由で機能しませんでしたが、代わりに最初の方法を選択しました。
Raj Pawan Gumdal 2014

4
このdispatch_async(dispatch_get_main_queue())方法が機能することは保証されていません。私はそれを使って非決定的な動作を確認しています。この場合、完了ブロックの前、および完了後に、システムがlayoutSubviewsとセルのレンダリングを完了することがあります。私のために働いた答えを以下に投稿します。
タイラーシェファー2016

3
dispatch_async(dispatch_get_main_queue())常に働くとは限らないことに同意します。ここにランダムな結果が表示されます。
Vojto 2017年

1
メインスレッドはを実行しNSRunLoopます。実行ループにはさまざまなフェーズがあり、(を使用してCFRunLoopObserver)特定のフェーズのコールバックをスケジュールできます。UIKitは、イベントハンドラーが戻った後のフェーズでレイアウトが実行されるようにスケジュールします。
rob mayoff 2017

106

迅速:

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
これは、「近い将来」にメインスレッドで何かをディスパッチすることと同じです。メインスレッドが完了ブロックをデキューする前に、テーブルビューがオブジェクトをレンダリングしているのを見ているだけの可能性があります。そもそもこの種のハックを行うことはお勧めしませんが、いずれにしても、これを試みる場合はdispatch_afterを使用する必要があります。
2014年

1
Robのソリューションは優れていますが、テーブルビューに行がない場合は機能しません。Avielのソリューションには、テーブルに行がなくセクションのみが含まれている場合でも機能するという利点があります。
Chrstph SLN 2014

@Christophe今のところ、モックビューコントローラーでtableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intメソッドをオーバーライドし、リロードが完了したことを通知したいものをオーバーライドに挿入することで、行なしでテーブルビューでRobの更新を使用できました。
Gobe 2016

49

Xcode 8.2.1、iOS 10、Swift 3以降

tableView.reloadData()CATransactionブロックを使用すると、終了を簡単に判断できます。

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

上記は、UICollectionViewのreloadData()およびUIPickerViewのreloadAllComponents()の終わりを決定するためにも機能します。


tableまた、や内で、テーブルビューの行を手動で挿入、削除、移動するなど、カスタムの再読み込みを実行している場合にも機能beginUpdatesendUpdatesます。
Darrarski、2018年

これは実際には現代的な解決策だと思います。実際、これはiOSの一般的なパターンです。例:stackoverflow.com/a/47536770/294884
Fattie

私はこれを試しました。私は非常に奇妙な行動をしました。テーブルビューに2つのheaderViewが正しく表示されます。setCompletionBlock私のnumberOfSectionsショー2の中では...これまでのところとても良いです。それでもsetCompletionBlock私が内部にいる場合、tableView.headerView(forSection: 1)それは戻りますnil!!! したがって、私はこのブロックがリロードの前に起こっているか、何かの前にキャプチャされているか、何か間違っているのだと思います。ちなみに私はタイラーの答えを試しましたが、うまくいきました!@Fattie
ハニー

32

上記のdispatch_async(dispatch_get_main_queue())方法が機能することは保証されていません。私はそれを使って非決定的な動作を確認しています。この場合、完了ブロックの前、および完了後に、システムがlayoutSubviewsとセルのレンダリングを完了することがあります。

これは、iOS 10で100%機能するソリューションです。UITableViewまたはUICollectionViewをカスタムサブクラスとしてインスタンス化する機能が必要です。以下はUICollectionViewソリューションですが、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

使用例:

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

この回答のSwiftバージョンについては、こちらをご覧ください


これが唯一の正しい方法です。アニメーションのためにいくつかのセルの最終フレームが必要だったので、それをプロジェクトに追加しました。Swiftの編集も追加しました。😉あなたが気にしないホープ
ジョン・フォーゲル

2
のブロックを呼び出した後は、必ずしも呼び出される必要がないため、への後続の呼び出しとしてlayoutSubviews設定する必要があります。強い参照が保持されているためブロックが実行されますが、これは望ましい動作ではありません。nillayoutSubviewsreloadData
Mark Bourke 2017年

なぜこれをUITableViewに使用できないのですか?目に見えるインターフェースはありません。ヘッダーファイルもインポートしましたが、変更はありません
Julfikar、2017年

2
この回答の補遺は、既存のコールバックが1つしかない場合、既存のコールバックを上書きできる可能性があることです。つまり、複数の呼び出し元が競合状態になります。解決策はreloadDataCompletionBlock、ブロックの配列を作成し、実行時にそれらを繰り返し、その後配列を空にすることです。
タイラーSheaffer 2017年

1)これはロブの最初の答え、つまり、layoutIfNeededを使用することと同等ではありませんか?2)なぜiOS 10について言及したのですか?iOS 9では機能しませんか?!
Honey

30

私はタイラーシェファーと同じ問題を抱えていました。

私は彼のソリューションをSwift に実装、それが私の問題を解決しました。

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

スウィフト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()
  }
}

使用例:

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

1
いいね!小さなNIT-ピックは、あなたが削除することができますif let言ってreloadDataCompletionBlock?()いないゼロ💥IFFを呼ぶであろう
タイラーシェーファー

ios9での私の状況では、これでうまくいきません
Matjan

self.reloadDataCompletionBlock? { completion() }されるべきだったself.reloadDataCompletionBlock?()
エメム'26

テーブルビューの高さのサイズ変更をどのように処理しますか?以前私はtableView.beginUpdates()tableView.layoutIfNeeded()を呼び出していましたtableView.endUpdates()
Parth Tamane

10

そして、UICollectionViewkolaworldの回答に基づくバージョン:

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

テストが必要です。これまでのところ、iOS 9.2、Xcode 9.2ベータ2で動作し、collectionViewをクロージャとしてインデックスにスクロールします。

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

使用法:

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

6

人々はまだこの質問と答えを読んでいるようです。そのB / C、私は私の答えを編集して、これとは実際には無関係なSynchronousという単語を削除しています。

When [tableView reloadData]を返し、tableViewの背後にある内部データ構造が更新されました。したがって、メソッドが完了したら、安全に下部までスクロールできます。私は自分のアプリでこれを確認しました。@ rob-mayoffによって広く受け入れられている回答は、用語でも混乱していますが、彼の最後の更新で同じことを認めています。

あなたの場合はtableView一番下までスクロールしていない、あなたが投稿していない他のコードに問題があることがあります。おそらく、スクロールの完了後にデータを変更していて、リロードしたり下部にスクロールしたりしていないのでしょうか

次のようにログを追加して、テーブルデータが正しいことを確認しますreloadData。サンプルアプリに次のコードがあり、完全に機能します。

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

質問を更新しました。NSLogがこのように出力される理由を知っていますか?
アラン

8
reloadData同期していません。以前は-この回答を参照してください:stackoverflow.com/a/16071589/193896
bendytree 2013年

1
同期しています。サンプルアプリを使用すると、これをテストして簡単に確認できます。この質問で@robの回答にリンクしました。下部にある彼の更新を読むと、彼もこれを確認しています。おそらく、視覚的なレイアウトの変更について話しているのでしょう。tableViewが目に見える形で同期的に更新されるのではなく、データが更新されることは事実です。これが、OPが必要とする値がreloadData戻り直後に正しい理由です。
XJones 2013年

1
で何が起こると予想されるかについて混乱するかもしれませんreloadData。が表示されない場合は意味がないb / c行をviewWillAppear受け入れるために私のテストケースを使用します。インスタンスにキャッシュされたデータが更新され、同期していることがわかります。がレイアウトされているときに呼び出された他のデリゲートメソッドを参照している場合、それらが表示されていなければ呼び出されません。私があなたのシナリオを誤解している場合は説明してください。scrollToRowAtIndexPath:tableViewreloadDatatableViewreloadDatatableViewtableViewtableView
XJones 2014年

3
楽しい時間。それは2014年であり、いくつかのメソッドが同期で非同期であるかどうかについての議論があります。当て推量のように感じます。すべての実装の詳細は、そのメソッド名の背後に完全に不透明です。プログラミングは素晴らしいと思いませんか?
fatuhoku 14

5

私はこのトリックを使用していますが、この質問の複製にすでに投稿しています。

-(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ポジティ​​ブなコメントなのかネガティブなコメントなのかわかりません。しかし、あなたが別の答えに「これが最良の解決策のようだ!」とコメントしているのを見ましたなので、比較的言えば、あなたはこのソリューションが最良であるとは考えていません。
・クール

1
偽のアニメーションの副作用に依存していますか?それは決して良い考えではありません。セレクターまたはGCDの実行を学び、適切に実行します。ところで、テーブルが読み込まれるメソッドができました。他の方法ではなくコードを呼び出すフレームワークであるため、問題のないプライベートプロトコルを使用することを気にしない場合は、このメソッドを使用できます。
マルハル

3

実際、これは私の問題を解決しました:

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

この方法で試してみてください

[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

テーブルが完全に読み込まれたときに実行します

その他の解決策は、UITableViewをサブクラス化できることです。


1

最終的には、Shawnのソリューションのバリエーションを使用しました。

デリゲートを使用してカスタムUITableViewクラスを作成します。

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

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

次に、私のコードでは、

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

また、インターフェースビルダーでテーブルビューをCustomTableViewに設定してください。

ここに画像の説明を入力してください


これは機能しますが、問題は、メソッドが1つのセルをロードするたびにヒットすることであり、テーブルビュー全体の再ロードではないため、この回答は、質問に対するものではありません。
Yash Bedi

確かに、それは複数回呼び出されますが、すべてのセルではありません。したがって、最初のデリゲートをリッスンし、reloadDataを再度呼び出すまで残りを無視できます。
サム・

1

Swift 3.0以降UITableViewではescaped Closure、以下のような拡張機能を作成できます。

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

そして、あなたが望むどこでも以下のようにそれを使用してください:

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

これが誰かに役立つことを願っています。乾杯!


素晴らしいアイデア..唯一のことは、混乱を避けるためだけです。reloadData()ではなく、t reloadの関数名を変更しました。ありがとう
Vijay Kumar AB

1

細部

  • Xcodeバージョン10.2.1(10E1001)、Swift 5

解決

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

使用法

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

完全なサンプル

ここにソリューションコード追加することを忘れないでください

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

結果

ここに画像の説明を入力してください


0

完了が「最後に見える」セルに送られるという考えに基づいて、別のアプローチを提供するだけ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
}

考えられる1つの問題はreloadData()lastIndexPathToDisplayが設定される前に完了した場合、「最後に表示された」セルlastIndexPathToDisplayが設定される前に表示され、完了が呼び出されない(そして「待機」状態になる)です。

self.mainTable.reloadData()

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

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

逆にすると、beforeのスクロールによってトリガーが完了してしまう可能性があり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

これを試して:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

reloadData()関数が完了すると、tableViewの色が黒から緑に変わります。


0

uitableviewのperformBatchUpdates関数を使用できます

ここであなたが達成することができる方法です

self.tableView.performBatchUpdates({

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

        //Reload Completed Use your code here
    }

0

CATransactionの再利用可能な拡張の作成:

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

次に、CATransactionの拡張メソッドを使用するUITableViewの拡張を作成します。

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

使用法:

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

-2

データをリロードした後に何かをするためにそれを使うことができます:

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

-20

遅延を設定してみてください:

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

14
これは危険です。遅延よりもリロードに時間がかかる場合はどうなりますか?
rob
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.