208

実行が完了した後、 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);

コンソールでは、Sections = 1, Row = -1; が返されます。

まったく同じ NSLogs を実行するcellForRowAtIndexPathと、セクション = 1 と行 = 8 になります。(8が正解)

4

18 に答える 18

305

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

したがって、テーブル ビューのリロード後に何かを実行する 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];

もう 1 つの方法は、レイアウト後のコードを後で実行するようにスケジュールすること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:reloadDatatableView:heightForRowAtIndexPath:reloadData

tableView:cellForRowAtIndexPath:ただし、テーブル ビューはレイアウト フェーズまで送信しませんtableView:headerViewForSection。レイアウト フェーズは、制御を実行ループに戻すときにデフォルトで発生します。

また、小さなテスト プログラムでは、特別なこと (送信や使用など) を行わなくても、質問のコードがテーブル ビューの一番下まで適切にスクロールされることもわかりました。layoutIfNeededdispatch_async

于 2013-04-17T22:49:48.343 に答える
113

迅速:

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

// ...somewhere later...

tableView.reloadData {
    print("done")
}

目的 C:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];
于 2013-09-15T13:17:08.833 に答える
60

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() の終了を決定するためにも機能します。

于 2017-04-01T21:29:48.510 に答える
36

上記のdispatch_async(dispatch_get_main_queue())方法の動作は保証されていません。システムがレイア​​ウトサブビューとセルのレンダリングを完了ブロックの前に完了したり、完了ブロックの後に完了したりする非決定論的な動作が見られます。

これは、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 バージョンについては、こちらを参照してください

于 2016-09-22T20:49:20.610 に答える
30

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

彼のソリューションを 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
}
于 2016-09-30T18:40:55.337 に答える
11

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

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

テストが必要です。これまでのところ、iOS 9.2、Xcode 9.2 beta 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)
}
于 2017-11-13T02:29:03.903 に答える
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;
}
于 2016-10-18T18:17:03.933 に答える
4

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

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

NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]];
if (visibleSections) {
    // hide the activityIndicator/Loader
}}
于 2015-06-02T13:20:12.457 に答える
1

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

[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をサブクラス化できることです

于 2014-10-02T15:14:33.873 に答える
-2

データをリロードした後に何かを行うために使用できます。

[UIView animateWithDuration:0 animations:^{
    [self.contentTableView reloadData];
} completion:^(BOOL finished) {
    _isUnderwritingUpdate = NO;
}];
于 2015-10-29T13:58:52.860 に答える