Làm thế nào để biết khi nào UITableView đã hoàn thành Tải lại?


183

Tôi đang cố gắng cuộn xuống dưới cùng của UITableView sau khi nó được thực hiện xong [self.tableView reloadData]

Ban đầu tôi có

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

Nhưng sau đó tôi đọc mà reloadData là không đồng bộ, do đó di chuyển không xảy ra kể từ khi self.tableView, [self.tableView numberOfSections][self.tableView numberOfRowsinSectionđều là 0.

Cảm ơn!

Điều kỳ lạ là tôi đang sử dụng:

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

Trong bảng điều khiển, nó trả về Phần = 1, Row = -1;

Khi tôi thực hiện cùng một NSLogs chính xác, cellForRowAtIndexPathtôi nhận được Phần = 1 và Hàng = 8; (8 là đúng)


Bản sao có thể có của câu hỏi này: stackoverflow.com/questions/4163579/ từ
pmk

2
giải pháp tốt nhất tôi đã thấy. stackoverflow.com/questions/1483581/ cường
Khaled Annajar

Câu trả lời của tôi cho những điều sau đây có thể giúp bạn, stackoverflow.com/questions/4163579/iêu
Suhas Aithal

Hãy thử câu trả lời của tôi ở đây - stackoverflow.com/questions/4163579/ từ
Suhas Aithal

Câu trả lời:


288

Việc tải lại xảy ra trong lần chuyển bố trí tiếp theo, điều này thường xảy ra khi bạn trả lại quyền điều khiển cho vòng lặp chạy (sau, giả sử, hành động nút của bạn hoặc bất kỳ điều gì trả về).

Vì vậy, một cách để chạy một cái gì đó sau khi tải lại chế độ xem bảng chỉ đơn giản là buộc chế độ xem bảng thực hiện bố cục ngay lập tức:

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

Một cách khác là lên lịch mã sau bố cục của bạn để chạy sau bằng cách sử dụng 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];
});

CẬP NHẬT

Sau khi điều tra thêm, tôi thấy rằng chế độ xem bảng gửi tableView:numberOfSections:tableView:numberOfRowsInSection:đến nguồn dữ liệu của nó trước khi quay trở lại reloadData. Nếu đại biểu thực hiện tableView:heightForRowAtIndexPath:, chế độ xem bảng cũng sẽ gửi (cho mỗi hàng) trước khi trở về reloadData.

Tuy nhiên, chế độ xem bảng không gửi tableView:cellForRowAtIndexPath:hoặc tableView:headerViewForSectioncho đến giai đoạn bố trí, điều này xảy ra theo mặc định khi bạn trả lại quyền điều khiển cho vòng lặp chạy.

Tôi cũng thấy rằng trong một chương trình thử nghiệm nhỏ, mã trong câu hỏi của bạn sẽ cuộn xuống dưới cùng của chế độ xem bảng mà không cần tôi làm gì đặc biệt (như gửi layoutIfNeededhoặc sử dụng dispatch_async).


3
@rob, tùy thuộc vào nguồn dữ liệu bảng của bạn lớn như thế nào, bạn có thể làm động để đi đến cuối bảng xem trong cùng một vòng lặp chạy. Nếu bạn thử mã kiểm tra của mình với một bảng lớn, mẹo sử dụng GCD của bạn để trì hoãn cuộn cho đến khi vòng chạy tiếp theo sẽ hoạt động, trong khi đó việc cuộn ngay lập tức sẽ thất bại. Nhưng dù sao, cảm ơn vì thủ thuật này !!
Ông T

7
Phương pháp 2 không làm việc cho tôi vì một số lý do không rõ, nhưng đã chọn phương pháp đầu tiên thay thế.
Raj Pawan Gumdal

4
các dispatch_async(dispatch_get_main_queue())phương pháp không được bảo đảm để làm việc. Tôi đang thấy hành vi không xác định với nó, trong đó đôi khi hệ thống đã hoàn thành layoutSubview và kết xuất ô trước khối hoàn thành và đôi khi sau đó. Tôi sẽ đăng một câu trả lời làm việc cho tôi dưới đây.
Tyler Sheaffer

3
Đồng ý với dispatch_async(dispatch_get_main_queue())không phải lúc nào cũng làm việc. Xem kết quả ngẫu nhiên ở đây.
Vojto

1
Các chủ đề chính chạy một NSRunLoop. Một vòng lặp chạy có các giai đoạn khác nhau và bạn có thể lên lịch gọi lại cho một giai đoạn cụ thể (sử dụng a CFRunLoopObserver). Bố trí lịch trình UIKit sẽ xảy ra trong giai đoạn sau, sau khi trình xử lý sự kiện của bạn trở lại.
cướp mayoff 30/12/17

106

Nhanh:

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

...somewhere later...

tableView.reloadData {
    println("done")
}

Mục tiêu-C:

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

16
Điều đó tương đương với việc gửi một cái gì đó trên luồng chính trong "tương lai gần". Có thể bạn chỉ đang xem chế độ xem bảng hiển thị các đối tượng trước khi luồng chính xử lý khối hoàn thành. Bạn không nên thực hiện loại hack này ngay từ đầu, nhưng trong mọi trường hợp, bạn nên sử dụng Clark_ sau khi bạn sẽ thử cách này.
seo

1
Giải pháp của Rob là tốt nhưng không hoạt động nếu không có hàng trong bảng xem. Giải pháp của Aviel có lợi thế để hoạt động ngay cả khi bảng không chứa các dòng mà chỉ có các phần.
Chrstph SLN

@Christophe Đến bây giờ, tôi đã có thể sử dụng bản cập nhật của Rob trong chế độ xem bảng mà không có bất kỳ hàng nào bằng cách tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intghi đè lên trình điều khiển chế độ xem Mock của tôi phương thức và chèn vào ghi đè của tôi bất cứ điều gì tôi muốn thông báo rằng việc tải lại đã kết thúc.
Gobe

49

Kể từ Xcode 8.2.1, iOS 10 và swift 3,

Bạn có thể xác định kết thúc tableView.reloadData()dễ dàng bằng cách sử dụng khối CATransaction:

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

Ở trên cũng hoạt động để xác định sự kết thúc của reloadData () và reload ALLComponents () của UICollectionView.


Tôi cũng hoạt động nếu bạn đang thực hiện tải lại tùy chỉnh, như chèn thủ công, xóa hoặc di chuyển các hàng trong chế độ xem bảng, trong beginUpdatesendUpdatescác cuộc gọi.
Darrarski

Tôi tin rằng đây thực sự là giải pháp hiện đại. thực sự đó là mô hình phổ biến trong iOS, ví dụ ... stackoverflow.com/a/48536770/294884
Fattie

Tôi đã thử điều này. Tôi có một hành vi rất kỳ quặc. Chế độ xem bảng của tôi hiển thị chính xác hai tiêu đề. Bên trong chương trình setCompletionBlockcủa tôi numberOfSections2 ... cho đến nay rất tốt. Tuy nhiên, nếu bên trong setCompletionBlocktôi làm điều tableView.headerView(forSection: 1)đó trở lại nil!!! do đó tôi nghĩ rằng khối này xảy ra trước khi tải lại hoặc chụp một cái gì đó trước đó hoặc tôi đang làm gì đó sai. FYI Tôi đã thử câu trả lời của Tyler và điều đó đã làm việc! @Fattie
Mật ong

32

Các dispatch_async(dispatch_get_main_queue())phương pháp trên là không được bảo đảm để làm việc . Tôi đang thấy hành vi không xác định với nó, trong đó đôi khi hệ thống đã hoàn thành layoutSubview và kết xuất ô trước khối hoàn thành và đôi khi sau đó.

Đây là một giải pháp hoạt động 100% cho tôi, trên iOS 10. Nó yêu cầu khả năng khởi tạo UITableView hoặc UICollectionView như một lớp con tùy chỉnh. Đây là giải pháp UICollectionView, nhưng nó hoàn toàn giống với 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

Ví dụ sử dụng:

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

Xem ở đây để biết phiên bản Swift của câu trả lời này


Đây là cách duy nhất đúng. Đã thêm nó vào dự án của tôi vì tôi cần các khung cuối cùng của một số ô cho mục đích hoạt hình. Tôi cũng đã thêm và chỉnh sửa cho Swift. Hy vọng bạn không phiền
Jon Vogel

2
Sau khi bạn gọi khối trong layoutSubviewsđó, nó sẽ được đặt thành nilcác cuộc gọi tiếp theo layoutSubviews, không nhất thiết là do reloadDatađược gọi, sẽ dẫn đến việc khối được thực thi vì có một tham chiếu mạnh được giữ, không phải là hành vi mong muốn.
Mark Bourke

Tại sao tôi không thể sử dụng cái này cho UITableView? Nó không hiển thị giao diện. Tôi cũng đã nhập tệp tiêu đề nhưng vẫn giống nhau
Julfikar

2
Một phụ lục cho câu trả lời này là có thể ghi lại cuộc gọi lại hiện tại nếu chỉ có một, có nghĩa là nhiều người gọi sẽ có điều kiện cuộc đua. Giải pháp là tạo ra reloadDataCompletionBlockmột mảng các khối và lặp lại chúng khi thực hiện và làm trống mảng sau đó.
Tyler Sheaffer

1) không tương đương với câu trả lời đầu tiên của Rob tức là sử dụng layout IfNeeded? 2) tại sao bạn lại đề cập đến iOS 10, nó không hoạt động trên iOS 9?!
Mật ong

30

Tôi đã có những vấn đề tương tự như Tyler Sheaffer.

Tôi đã thực hiện giải pháp của anh ấy trong Swift và nó đã giải quyết vấn đề của tôi.

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

Cách sử dụng ví dụ:

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

1
đẹp! chọn nit nhỏ, bạn có thể loại bỏ if letbằng cách nói rằng reloadDataCompletionBlock?()sẽ gọi iff không phải là 💥
Tyler Sheaffer

Không có may mắn với điều này trong tình huống của tôi trên ios9
Matjan

self.reloadDataCompletionBlock? { completion() }nên làself.reloadDataCompletionBlock?()
emem

Làm cách nào để xử lý thay đổi kích thước chiều cao của bảng xem? Trước đây tôi đã gọi bảngView.beginUpdates () tableView.layout IfNeeded () tableView.endUpdates ()
Parth Tamane 15/11/19

10

Và một UICollectionViewphiên bản, dựa trên câu trả lời của kolaworld:

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

Cần thử nghiệm. Hoạt động cho đến nay trên iOS 9.2, Xcode 9.2 beta 2, với việc cuộn bộ sưu tập Xem đến một chỉ mục, dưới dạng đóng.

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

Sử dụng:

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

6

Có vẻ như mọi người vẫn đang đọc câu hỏi này và câu trả lời. B / c về điều đó, tôi đang chỉnh sửa câu trả lời của mình để xóa từ Đồng bộ hóa thực sự không liên quan đến điều này.

When [tableView reloadData]trả về, các cấu trúc dữ liệu nội bộ phía sau bảngView đã được cập nhật. Do đó, khi phương thức hoàn thành, bạn có thể cuộn xuống phía dưới một cách an toàn. Tôi đã xác minh điều này trong ứng dụng của riêng tôi. Câu trả lời được chấp nhận rộng rãi bởi @ rob-mayoff, trong khi cũng khó hiểu về thuật ngữ, thừa nhận điều tương tự trong bản cập nhật cuối cùng của ông.

Nếu là của bạn tableView không cuộn xuống phía dưới, bạn có thể gặp sự cố trong mã khác mà bạn chưa đăng. Có lẽ bạn đang thay đổi dữ liệu sau khi cuộn xong và bạn không tải lại và / hoặc cuộn xuống phía dưới?

Thêm một số ghi nhật ký như sau để xác minh rằng dữ liệu bảng là chính xác sau reloadData. Tôi có mã sau đây trong một ứng dụng mẫu và nó hoạt động hoàn hảo.

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

Tôi cập nhật câu hỏi của tôi. Bạn có biết tại sao NSLogs của tôi sẽ xuất ra như thế này không?
Alan

8
reloadDatakhông đồng bộ. Nó được sử dụng - xem câu trả lời này: stackoverflow.com/a/16071589/193896
uốn cong

1
Nó là đồng bộ. Thật dễ dàng để kiểm tra và thấy điều này với một ứng dụng mẫu. Bạn đã liên kết với câu trả lời của @ rob trong câu hỏi này. Nếu bạn đọc cập nhật của anh ấy ở phía dưới, anh ấy cũng đã xác minh điều này. Có lẽ bạn đang nói về những thay đổi bố cục hình ảnh. Đúng là bảngView không được cập nhật rõ ràng đồng bộ nhưng dữ liệu là. Đó là lý do tại sao các giá trị mà OP cần là chính xác ngay sau khi reloadDatatrả về.
XJones

1
Bạn có thể bối rối về những gì được dự kiến ​​sẽ xảy ra reloadData. Sử dụng trường hợp thử nghiệm của tôi để viewWillAppearchấp nhận cho scrollToRowAtIndexPath:dòng b / c là vô nghĩa nếu tableViewkhông hiển thị. Bạn sẽ thấy rằng reloadDatađã cập nhật dữ liệu được lưu trong bộ nhớ cache tableViewvà đó reloadDatalà đồng bộ. Nếu bạn đang đề cập đến các tableViewphương thức ủy nhiệm khác được gọi khi tableViewbố trí đang được bố trí thì chúng sẽ không được gọi nếu tableViewkhông được hiển thị. Nếu tôi hiểu nhầm kịch bản của bạn xin vui lòng giải thích.
XJones

3
Những lúc vui vẻ. Đó là năm 2014 và có nhiều tranh cãi về việc liệu một số phương pháp có đồng bộ và không đồng bộ hay không. Cảm giác như phỏng đoán. Tất cả các chi tiết thực hiện là hoàn toàn mờ đục đằng sau tên phương thức đó. Không phải là lập trình tuyệt vời sao?
fatuhoku

5

Tôi sử dụng thủ thuật này, khá chắc chắn rằng tôi đã đăng nó lên một bản sao của câu hỏi này:

-(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 không rõ nếu bạn muốn bình luận tích cực hay bình luận tiêu cực. Nhưng tôi thấy bạn đã nhận xét một câu trả lời khác là "đây dường như là giải pháp tốt nhất!" , vì vậy tôi đoán rằng tương đối mà nói, bạn không coi giải pháp này là tốt nhất.
Cœur

1
Dựa vào một bên ảnh hưởng của một hình ảnh động giả? Không có cách nào là một ý tưởng tốt. Tìm hiểu thực hiện bộ chọn hoặc GCD và làm điều đó đúng. Btw hiện đã có một phương thức được tải bảng mà bạn chỉ có thể sử dụng nếu bạn không phiền khi sử dụng một giao thức riêng có thể sẽ ổn vì đó là khung gọi mã của bạn chứ không phải là cách khác.
malhal

3

Trên thực tế điều này đã giải quyết vấn đề của tôi:

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

Hãy thử cách này nó sẽ hoạt động

[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

Tôi sẽ thực thi khi bảng được tải hoàn toàn

Giải pháp khác là bạn có thể phân lớp UITableView


1

Tôi đã kết thúc bằng cách sử dụng một biến thể của giải pháp của Shawn:

Tạo một lớp UITableView tùy chỉnh với một đại biểu:

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

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

Sau đó, trong mã của tôi, tôi sử dụng

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

Đồng thời đảm bảo rằng bạn đã đặt chế độ xem bảng của mình thành CustomTableView trong trình tạo giao diện:

nhập mô tả hình ảnh ở đây


Điều này hoạt động nhưng vấn đề là phương pháp bị tấn công mỗi khi nó thực hiện tải một ô duy nhất, KHÔNG PHẢI LÀ BẢNG XEM BẢNG XÁC NHẬN, vì vậy rõ ràng câu trả lời này không liên quan đến câu hỏi được hỏi.
Yash Bedi

Đúng, nó được gọi nhiều hơn một lần, nhưng không phải trên mọi ô. Vì vậy, bạn có thể lắng nghe đại biểu đầu tiên và bỏ qua phần còn lại cho đến khi bạn gọi lại reloadData.
Sam

1

Trong Swift 3.0 + chúng ta có thể tạo ra một một phần mở rộng cho UITableViewvới một escaped Closurenhư dưới đây:

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

Và sử dụng nó như bên dưới bất cứ nơi nào bạn muốn:

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

hy vọng điều này sẽ giúp cho một ai đó. chúc mừng


Ý tưởng tuyệt vời..chỉ là, để tránh nhầm lẫn, tôi đã thay đổi tên hàm t tải lại, thay vì reloadData (). Cảm ơn
Vijay Kumar AB

1

Chi tiết

  • Phiên bản Xcode 10.2.1 (10E1001), Swift 5

Giải pháp

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

Sử dụng

Xem UITable

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

Xem UICollection

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

Mẫu đầy đủ

Đừng quên thêm mã giải pháp tại đây

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

Các kết quả

nhập mô tả hình ảnh ở đây


0

Chỉ cần đưa ra một cách tiếp cận khác, dựa trên ý tưởng về việc hoàn thành là ô 'hiển thị cuối cùng' được gửi đến 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
}

Một vấn đề có thể xảy ra là: Nếu reloadData()đã kết thúc trước khi lastIndexPathToDisplayđược đặt, ô 'hiển thị cuối cùng' sẽ được hiển thị trước khi lastIndexPathToDisplayđược đặt và hoàn thành sẽ không được gọi (và sẽ ở trạng thái 'chờ'):

self.mainTable.reloadData()

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

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

Nếu chúng tôi đảo ngược, chúng tôi có thể kết thúc với việc hoàn thành được kích hoạt bằng cách cuộn trước đó 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

Thử cái này:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

Màu bảngView sẽ thay đổi từ đen sang xanh chỉ sau khi reloadData()chức năng hoàn thành.


0

Bạn có thể sử dụng chức năng biểu diễnBatchUpdates của uitableview

Đây là cách bạn có thể đạt được

self.tableView.performBatchUpdates({

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

        //Reload Completed Use your code here
    }

0

Tạo một phần mở rộng có thể tái sử dụng của CATransaction:

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

Hiện đang tạo tiện ích mở rộng của UITableView sẽ sử dụng phương thức tiện ích mở rộng của CATransaction:

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

Sử dụng:

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

-2

Bạn có thể sử dụng nó để làm một cái gì đó sau khi tải lại dữ liệu:

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

-20

Hãy thử thiết lập độ trễ:

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

14
Điều này nguy hiểm. Điều gì nếu mất nhiều thời gian để tải lại hơn sự chậm trễ của bạn?
cướp
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.