Як сказати, коли 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;

Коли я роблю такі самі NSLogs, cellForRowAtIndexPathя отримую Sections = 1 та Row = 8; (Вірно 8)


Можливий дублікат цього питання: stackoverflow.com/questions/4163579 / ...
ПМК

2
найкраще рішення, яке я бачив. stackoverflow.com/questions/1483581 / ...
Халед Annajar

Моя відповідь на наступне може допомогти вам, stackoverflow.com/questions/4163579 / ...
Suhas Aithal

Спробуйте моя відповідь тут - stackoverflow.com/questions/4163579 / ...
Suhas Aithal

Відповіді:


288

Перезавантаження відбувається під час наступного пропуску макета, що зазвичай відбувається, коли ви повертаєте керування до циклу запуску (після, скажімо, дії вашої кнопки чи чого завгодно).

Тож один із способів запустити щось після перезавантаження огляду таблиці - просто змусити огляд таблиці негайно виконати макет:

[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).


3
@rob, залежно від того, наскільки велике джерело даних вашої таблиці, ви можете анімувати, переходячи до нижньої частини таблиці, у тому ж циклі запуску. Якщо ви спробуєте свій тестовий код з величезною таблицею, ваш трюк із використанням GCD для затримки прокрутки до наступного циклу запуску спрацює, тоді як негайно прокрутка не завершиться. Але все одно, дякую за цю хитрість !!
Містер T

7
Метод 2 не працював для мене з незрозумілої причини, але обрав натомість перший метод.
Радж Паван Гумдал

4
dispatch_async(dispatch_get_main_queue())метод не гарантує роботу. Я бачу недетерміновану поведінку з ним, в якій іноді система завершує layoutSubviews і візуалізацію комірок перед блоком завершення, а іноді і після. Я опублікую відповідь, яка працювала на мене нижче.
Тайлер Шиффер

3
Погодьтеся, dispatch_async(dispatch_get_main_queue())не завжди працює. Тут можна побачити випадкові результати.
Войте

1
Основна нитка працює an NSRunLoop. Цикл запуску має різні фази, і ви можете запланувати зворотний виклик для певної фази (використовуючи a CFRunLoopObserver). Макет графіків UIKit має відбуватися на більш пізньому етапі, після повернення обробника подій.
грабувати майофф

106

Швидкий:

extension UITableView {
    func reloadData(completion:@escaping ()->()) {
        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...
}];

16
Це еквівалентно відправленню чогось на головну нитку в "найближчому майбутньому". Ймовірно, ви просто бачите, що подання таблиці відображає об'єкти, перш ніж головний потік видає блок завершення. Не рекомендується робити цей вид злому в першу чергу, але в будь-якому випадку вам слід скористатися dispatch_after, якщо ви збираєтеся це зробити.
seo

1
Рішення Роб є гарним, але не працює, якщо в перегляді таблиці немає рядків. Рішення Aviel має перевагу працювати навіть у тому випадку, коли в таблиці немає рядків, а лише розділи.
Chrstph SLN

@Christophe На даний момент мені вдалося використати оновлення Роба в поданні таблиці без будь-яких рядків, змінивши в моєму контролері перегляду Mock tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intметод і вставивши в мій перегляд те, що я хотів повідомити, що перезавантаження закінчилося.
Gobe

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

Вищезазначене також працює для визначення кінця reloadData () UICollectionView () та перезавантаження UIPickerViewAllComponents ().


👍 Я також працює, якщо ви виконуєте власну перезавантаження, наприклад, вручну вставляючи, видаляючи чи переміщуючи рядки у поданні таблиці, всередині beginUpdatesта endUpdatesвиклики.
Даррарський

Я вважаю, що це насправді сучасне рішення. насправді це загальна модель в iOS, наприклад ... stackoverflow.com/a/47536770/294884
Fattie

Я спробував це. Я мав дуже дивну поведінку. Мій перегляд таблиці правильно показує два заголовки. Всередині setCompletionBlockмоїх numberOfSectionsшоу 2 ... поки що добре. Але якщо всередині setCompletionBlockя tableView.headerView(forSection: 1)це повертається nil!!! отже, я думаю, що цей блок або відбувається перед перезавантаженням, або щось захоплює раніше, або я роблю щось не так. FYI Я спробував відповісти Тайлера, і це спрацювало! @Fattie
Honey

32

Наведений 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.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. Сподіваюсь, ви не заперечуєте 😉
Джон Фогель

2
Після виклику блоку в layoutSubviewsньому слід встановити nilнаступні дзвінки layoutSubviews, не обов'язково через reloadDataте, що вони викликаються, це призведе до того, що блок буде виконаний, оскільки існує чітке посилання, яке не є бажаною поведінкою.
Марк Бурк

чому я не можу використовувати це для UITableView? він не показує видимого інтерфейсу. Я імпортував файл заголовка також, але все одно той самий
Julfikar

2
Доповненням до цієї відповіді є те, що можна зупинити існуючий зворотний дзвінок, якщо є лише один, тобто декілька абонентів матимуть перегони. Рішення полягає в тому, щоб зробити reloadDataCompletionBlockмасив блоків і повторити їх над виконанням та виправити масив після цього.
Тайлер Шиффер

1) Це не еквівалент першої відповіді Роба, тобто використання layoutIfNeeded? 2) чому ви згадали iOS 10, це не працює на iOS 9 ?!
Мед

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
приємно! невеликий ніт-вибір, ви можете видалити if letвисловлювання, reloadDataCompletionBlock?()яке закличе iff not nil 💥
Тайлер

Не пощастило з цим в моїй ситуації на ios9
Matjan

self.reloadDataCompletionBlock? { completion() }мав бутиself.reloadDataCompletionBlock?()
emem

Як обробити розмір висоти подання таблиці? Раніше я закликав tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates ()
Parth Tamane

10

І UICollectionViewверсія, заснована на відповіді kolaworld:

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

Тестування потреб. Наразі працює на iOS 9.2, Xcode 9.2 beta 2, з прокруткою колекціїView до індексу, як закриття.

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

Здається, люди все ще читають це питання та відповіді. Ч / з цього, я редагую свою відповідь, щоб видалити слово 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];

Я оновив свої запитання. Чи знаєте ви, чому мої NSLogs видаватимуться так?
Алан

8
reloadDataне є синхронним. Раніше було - побачити цей відповідь: stackoverflow.com/a/16071589/193896
bendytree

1
Це синхронно. Це дуже легко протестувати і побачити це за допомогою прикладної програми. Ви пов’язали відповідь @ rob у цьому запитанні. Якщо ви читаєте його оновлення внизу, він також це підтвердив. Можливо, ви говорите про зміни візуального макета. Це правда, що tableView видимо не оновлюється синхронно, але дані є. Ось чому значення, потрібні ОП, є правильними відразу після reloadDataповернення.
XJones

1
Вас можуть збентежити, що очікується, що станеться в reloadData . Використовуйте мій тестовий випадок, viewWillAppearприймаючи scrollToRowAtIndexPath:рядок b / c, який є безглуздим, якщо tableViewвін не відображається. Ви побачите, що reloadDataоновили дані, кешовані в tableViewекземплярі, і reloadDataце синхронно. Якщо ви посилаєтесь на інші tableViewспособи делегування, викликані під tableViewчас розміщення, ті не будуть викликатися, якщо значення tableViewне відображається. Якщо я нерозумію ваш сценарій, будь ласка, поясніть.
XJones

3
Які веселі часи. Настав 2014 рік, і є аргументи щодо того, чи є якийсь метод синхронним і асинхронним чи ні. Відчуває здогадки. Усі деталі реалізації є абсолютно непрозорими за цією назвою методу. Не програмування чудово?
fatuhoku

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 незрозуміло, якщо ви розумієте це як позитивний коментар чи негативний коментар. Але я побачив, що ви прокоментували ще одну відповідь, оскільки "це здається найкращим рішенням!" , тож я здогадуюсь, що відносно кажучи, ви не вважаєте це рішення найкращим.
Cœur

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

Я в кінцевому підсумку використовував варіацію рішення Шона:

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

введіть тут опис зображення


це працює, але проблема полягає в тому, що метод потрапляє щоразу, коли виконується завантаження однієї комірки, а не ПОВЕРНЕННЯ ПОВЕРНЕННЯ ТАБЛИЦІ, тому однозначно ця відповідь не стосується заданого питання.
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")
 }

сподіваюся, що це комусь допоможе. ура!


Блискуча ідея. Тільки річ полягає лише в тому, щоб уникнути плутанини, я змінив ім'я функції t reload, а не reloadData (). Спасибі
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
}

Одне можливе питання: Якщо reloadData()завершено до lastIndexPathToDisplayвстановлення встановленого, клітинка "Останній видимий" буде показана раніше, ніж lastIndexPathToDisplayбуде встановлено, а завершення не буде викликано (і перебуває у стані "очікування"):

self.mainTable.reloadData()

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

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

Якщо ми повернемо назад, ми могли б закінчитися завершенням, викликаним прокруткою раніше 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

})

Колір tableView зміниться з чорного на зелений лише після завершення reloadData()функції.


0

Ви можете використовувати функцію performBatchUpdates uitableview

Ось як можна досягти

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

Тепер створюємо розширення UITableView, яке б використовувало метод розширення CATransaction:

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
Це небезпечно. Що робити, якщо для перезавантаження потрібно більше часу, ніж ваша затримка?
пограбувати
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.