Как сказать, когда UITableView завершил ReloadData?



Я пытаюсь прокрутить в нижней части 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 правильно)

824   13  

13 ответов:

перезагрузка происходит во время следующего прохода макета, что обычно происходит, когда вы возвращаете управление в цикл запуска (после, скажем, действия кнопки или любого другого возврата).

таким образом, один из способов запустить что-то после перезагрузки табличного представления-просто заставить табличное представление немедленно выполнить макет:

[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 до этапа компоновки, который происходит по умолчанию, когда вы возвращаете управление в цикл выполнения.

я также нахожу, что в крошечной тестовой программе, Код в вашем вопросе правильно прокручивается в нижней части таблицы,без я делаю что-то особенное (например, отправка layoutIfNeeded или через dispatch_async).

Swift:

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

...somewhere later...

tableView.reloadData {
    println("done")
}

Цель-C:

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

The dispatch_async(dispatch_get_main_queue()) способ выше не гарантируется работа. Я вижу недетерминированное поведение с ним, в котором иногда система завершила layoutSubviews и рендеринг ячейки до блока завершения, а иногда и после.

вот решение, которое работает на 100% для меня, на iOS 10. Для этого требуется возможность создания экземпляра UITableView или UICollectionView в качестве пользовательского подкласса. Вот решение UICollectionView, но это точно то же самое для UITableView:

CustomCollectionView.h:

#import <UIKit/UIKit.h>

@interface CustomCollectionView: UICollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock;

@end

CustomCollectionView.м:

#import "CustomCollectionView.h"

@interface CustomCollectionView ()

@property (nonatomic, copy) void (^reloadDataCompletionBlock)(void);

@end

@implementation CustomCollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock
{
    self.reloadDataCompletionBlock = completionBlock;
    [super reloadData];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.reloadDataCompletionBlock) {
        self.reloadDataCompletionBlock();
        self.reloadDataCompletionBlock = nil;
    }
}

@end

пример использования:

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

посмотреть здесь для быстрой версии этого ответа

начиная с 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 ().

у меня были те же проблемы, что и у Тайлера Шиффера.

я реализовал решение в 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
    super.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
      super.reloadData()
  }
}

Пример Использования:

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

похоже, люди все еще читают этот вопрос и ответы. B / c этого, я редактирую свой ответ, чтобы удалить слово синхронно который действительно не имеет отношения к этому.

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

и UICollectionView версия, основанная на ответе kolaworld:

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

Я использую этот трюк, довольно уверен, что я уже отправил его в дубликат этого вопроса:

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

на самом деле это решило мою проблему:

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

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

попробуйте этот способ он будет работать

[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

в итоге я использовал вариант решения Шона:

создайте пользовательский класс 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 в построителе интерфейса:

enter image description here

вы можете использовать его для сделать что-то после перезагрузки данных:

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

попробуйте установить задержки:

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

Comments

    Ничего не найдено.